changeset 6096:86a41c2bedec

upgrade passlib from 1.6.2 to 1.6.5
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 05 Sep 2016 23:44:04 +0200
parents 465cb6f5c6d7
children 815981fad7fd
files MoinMoin/support/passlib/__init__.py MoinMoin/support/passlib/_setup/docdist.py MoinMoin/support/passlib/_setup/stamp.py MoinMoin/support/passlib/apache.py MoinMoin/support/passlib/apps.py MoinMoin/support/passlib/context.py MoinMoin/support/passlib/exc.py MoinMoin/support/passlib/ext/django/models.py MoinMoin/support/passlib/ext/django/utils.py MoinMoin/support/passlib/handlers/bcrypt.py MoinMoin/support/passlib/handlers/cisco.py MoinMoin/support/passlib/handlers/des_crypt.py MoinMoin/support/passlib/handlers/digests.py MoinMoin/support/passlib/handlers/django.py MoinMoin/support/passlib/handlers/fshp.py MoinMoin/support/passlib/handlers/ldap_digests.py MoinMoin/support/passlib/handlers/md5_crypt.py MoinMoin/support/passlib/handlers/misc.py MoinMoin/support/passlib/handlers/mssql.py MoinMoin/support/passlib/handlers/oracle.py MoinMoin/support/passlib/handlers/pbkdf2.py MoinMoin/support/passlib/handlers/phpass.py MoinMoin/support/passlib/handlers/scram.py MoinMoin/support/passlib/handlers/sha1_crypt.py MoinMoin/support/passlib/handlers/sha2_crypt.py MoinMoin/support/passlib/handlers/sun_md5_crypt.py MoinMoin/support/passlib/handlers/windows.py MoinMoin/support/passlib/hosts.py MoinMoin/support/passlib/ifc.py MoinMoin/support/passlib/registry.py MoinMoin/support/passlib/utils/__init__.py MoinMoin/support/passlib/utils/_blowfish/__init__.py MoinMoin/support/passlib/utils/_blowfish/_gen_files.py MoinMoin/support/passlib/utils/_blowfish/base.py MoinMoin/support/passlib/utils/compat.py MoinMoin/support/passlib/utils/des.py MoinMoin/support/passlib/utils/handlers.py MoinMoin/support/passlib/utils/md4.py MoinMoin/support/passlib/utils/pbkdf2.py MoinMoin/support/passlib/win32.py
diffstat 40 files changed, 559 insertions(+), 349 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/passlib/__init__.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/__init__.py	Mon Sep 05 23:44:04 2016 +0200
@@ -1,3 +1,3 @@
-"""passlib - suite of password hashing & generation routinges"""
+"""passlib - suite of password hashing & generation routines"""
 
-__version__ = '1.6.2'
+__version__ = '1.6.5'
--- a/MoinMoin/support/passlib/_setup/docdist.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/_setup/docdist.py	Mon Sep 05 23:44:04 2016 +0200
@@ -1,4 +1,4 @@
-"custom command to build doc.zip file"
+"""custom command to build doc.zip file"""
 #=============================================================================
 # imports
 #=============================================================================
--- a/MoinMoin/support/passlib/_setup/stamp.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/_setup/stamp.py	Mon Sep 05 23:44:04 2016 +0200
@@ -1,4 +1,4 @@
-"update version string during build"
+"""update version string during build"""
 #=============================================================================
 # imports
 #=============================================================================
@@ -21,7 +21,7 @@
     return opts['cmdclass'].get(name) or Distribution().get_command_class(name)
 
 def stamp_source(base_dir, version, dry_run=False):
-    "update version string in passlib dist"
+    """update version string in passlib dist"""
     path = os.path.join(base_dir, "passlib", "__init__.py")
     with open(path) as fh:
         input = fh.read()
--- a/MoinMoin/support/passlib/apache.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/apache.py	Mon Sep 05 23:44:04 2016 +0200
@@ -188,7 +188,7 @@
 
     @property
     def mtime(self):
-        "modify time when last loaded (if bound to a local file)"
+        """modify time when last loaded (if bound to a local file)"""
         return self._mtime
 
     #===================================================================
@@ -240,13 +240,13 @@
         return True
 
     def load_string(self, data):
-        "Load state from unicode or bytes string, replacing current state"
+        """Load state from unicode or bytes string, replacing current state"""
         data = to_bytes(data, self.encoding, "data")
         self._mtime = 0
         self._load_lines(BytesIO(data))
 
     def _load_lines(self, lines):
-        "load from sequence of lists"
+        """load from sequence of lists"""
         # XXX: found reference that "#" comment lines may be supported by
         #      htpasswd, should verify this, and figure out how to handle them.
         #      if true, this would also affect what can be stored in user field.
@@ -262,15 +262,15 @@
             if key not in records:
                 records[key] = value
 
-    def _parse_record(cls, record, lineno): # pragma: no cover - abstract method
-        "parse line of file into (key, value) pair"
+    def _parse_record(self, record, lineno): # pragma: no cover - abstract method
+        """parse line of file into (key, value) pair"""
         raise NotImplementedError("should be implemented in subclass")
 
     #===================================================================
     # saving
     #===================================================================
     def _autosave(self):
-        "subclass helper to call save() after any changes"
+        """subclass helper to call save() after any changes"""
         if self.autosave and self._path:
             self.save()
 
@@ -289,26 +289,26 @@
                                self.__class__.__name__)
 
     def to_string(self):
-        "Export current state as a string of bytes"
+        """Export current state as a string of bytes"""
         return join_bytes(self._iter_lines())
 
     def _iter_lines(self):
-        "iterator yielding lines of database"
+        """iterator yielding lines of database"""
         return (self._render_record(key,value) for key,value in iteritems(self._records))
 
-    def _render_record(cls, key, value): # pragma: no cover - abstract method
-        "given key/value pair, encode as line of file"
+    def _render_record(self, key, value): # pragma: no cover - abstract method
+        """given key/value pair, encode as line of file"""
         raise NotImplementedError("should be implemented in subclass")
 
     #===================================================================
     # field encoding
     #===================================================================
     def _encode_user(self, user):
-        "user-specific wrapper for _encode_field()"
+        """user-specific wrapper for _encode_field()"""
         return self._encode_field(user, "user")
 
     def _encode_realm(self, realm): # pragma: no cover - abstract method
-        "realm-specific wrapper for _encode_field()"
+        """realm-specific wrapper for _encode_field()"""
         return self._encode_field(realm, "realm")
 
     def _encode_field(self, value, param="field"):
@@ -370,19 +370,39 @@
 # htpasswd editing
 #=============================================================================
 
-# FIXME: apr_md5_crypt technically the default only for windows, netware and tpf.
-# TODO: find out if htpasswd's "crypt" mode is a crypt() *call* or just des_crypt implementation.
-#       if the former, we can support anything supported by passlib.hosts.host_context,
-#       allowing more secure hashes than apr_md5_crypt to be used.
-#       could perhaps add this behavior as an option to the constructor.
+#: default CryptContext used by HtpasswdFile
+# TODO: update this to support everything in host_context (where available),
+#       and note in the documentation that the default is no longer guaranteed to be portable
+#       across platforms.
 #       c.f. http://httpd.apache.org/docs/2.2/programs/htpasswd.html
 htpasswd_context = CryptContext([
-    "apr_md5_crypt", # man page notes supported everywhere, default on Windows, Netware, TPF
-    "des_crypt", # man page notes server does NOT support this on Windows, Netware, TPF
-    "ldap_sha1", # man page notes only for transitioning <-> ldap
-    "plaintext" # man page notes server ONLY supports this on Windows, Netware, TPF
+    # man page notes supported everywhere; is default on Windows, Netware, TPF
+    "apr_md5_crypt",
+
+    # [added in passlib 1.6.3]
+    # apache requires host crypt() support; but can generate natively
+    # (as of https://bz.apache.org/bugzilla/show_bug.cgi?id=49288)
+    "bcrypt",
+
+    # [added in passlib 1.6.3]
+    # apache requires host crypt() support; and can't generate natively
+    "sha256_crypt",
+    "sha512_crypt",
+
+    # man page notes apache does NOT support this on Windows, Netware, TPF
+    "des_crypt",
+
+    # man page notes intended only for transitioning htpasswd <-> ldap
+    "ldap_sha1",
+
+    # man page notes apache ONLY supports this on Windows, Netware, TPF
+    "plaintext"
     ])
 
+#: scheme that will be used when 'portable' is requested.
+portable_scheme = "apr_md5_crypt"
+
+
 class HtpasswdFile(_CommonFile):
     """class for reading & writing Htpasswd files.
 
@@ -444,13 +464,23 @@
     :type default_scheme: str
     :param default_scheme:
         Optionally specify default scheme to use when encoding new passwords.
-        Must be one of ``"apr_md5_crypt"``, ``"des_crypt"``, ``"ldap_sha1"``,
-        ``"plaintext"``. It defaults to ``"apr_md5_crypt"``.
+        May be any of ``"bcrypt"``, ``"sha256_crypt"``, ``"apr_md5_crypt"``, ``"des_crypt"``,
+        ``"ldap_sha1"``, ``"plaintext"``. It defaults to ``"apr_md5_crypt"``.
+
+        .. note::
+
+            Some hashes are only supported by apache / htpasswd on certain operating systems
+            (e.g. bcrypt on BSD, sha256_crypt on linux).  To get the strongest
+            hash that's still portable, applications can specify ``default_scheme="portable"``.
 
         .. versionadded:: 1.6
             This keyword was previously named ``default``. That alias
             has been deprecated, and will be removed in Passlib 1.8.
 
+        .. versionchanged:: 1.6.3
+
+            Added support for ``"bcrypt"``, ``"sha256_crypt"``, and ``"portable"``.
+
     :type context: :class:`~passlib.context.CryptContext`
     :param context:
         :class:`!CryptContext` instance used to encrypt
@@ -464,7 +494,7 @@
 
             This option may be used to add support for non-standard hash
             formats to an htpasswd file. However, the resulting file
-            will probably not be usuable by another application,
+            will probably not be usable by another application,
             and particularly not by Apache.
 
     :param autoload:
@@ -546,6 +576,8 @@
                  DeprecationWarning, stacklevel=2)
             default_scheme = kwds.pop("default")
         if default_scheme:
+            if default_scheme == "portable":
+                default_scheme = portable_scheme
             context = context.copy(default=default_scheme)
         self.context = context
         super(HtpasswdFile, self).__init__(path, **kwds)
@@ -566,7 +598,7 @@
     #===================================================================
 
     def users(self):
-        "Return list of all users in database"
+        """Return list of all users in database"""
         return [self._decode_field(user) for user in self._records]
 
     ##def has_user(self, user):
@@ -605,7 +637,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="set_password")
     def update(self, user, password):
-        "set password for user"
+        """set password for user"""
         return self.set_password(user, password)
 
     def get_hash(self, user):
@@ -624,7 +656,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="get_hash")
     def find(self, user):
-        "return hash for user"
+        """return hash for user"""
         return self.get_hash(user)
 
     # XXX: rename to something more explicit, like delete_user()?
@@ -673,7 +705,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="check_password")
     def verify(self, user, password):
-        "verify password for user"
+        """verify password for user"""
         return self.check_password(user, password)
 
     #===================================================================
@@ -931,7 +963,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="set_password")
     def update(self, user, realm, password):
-        "set password for user"
+        """set password for user"""
         return self.set_password(user, realm, password)
 
     # XXX: rename to something more explicit, like get_hash()?
@@ -957,7 +989,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="get_hash")
     def find(self, user, realm):
-        "return hash for user"
+        """return hash for user"""
         return self.get_hash(user, realm)
 
     # XXX: rename to something more explicit, like delete_user()?
@@ -1025,7 +1057,7 @@
     @deprecated_method(deprecated="1.6", removed="1.8",
                        replacement="check_password")
     def verify(self, user, realm, password):
-        "verify password for user"
+        """verify password for user"""
         return self.check_password(user, realm, password)
 
     #===================================================================
--- a/MoinMoin/support/passlib/apps.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/apps.py	Mon Sep 05 23:44:04 2016 +0200
@@ -77,12 +77,12 @@
     all__vary_rounds = 0.1,
 
     # set a good starting point for rounds selection
-    sha512_crypt__min_rounds = 60000,
-    sha256_crypt__min_rounds = 80000,
+    sha512_crypt__min_rounds = 535000,
+    sha256_crypt__min_rounds = 535000,
 
     # if the admin user category is selected, make a much stronger hash,
-    admin__sha512_crypt__min_rounds = 120000,
-    admin__sha256_crypt__min_rounds = 160000,
+    admin__sha512_crypt__min_rounds = 1024000,
+    admin__sha256_crypt__min_rounds = 1024000,
     )
 
 #=============================================================================
@@ -132,7 +132,7 @@
     return ('ldap_' + name for name in unix_crypt_schemes)
 
 def _iter_ldap_schemes():
-    "helper which iterates over supported std ldap schemes"
+    """helper which iterates over supported std ldap schemes"""
     return chain(std_ldap_schemes, _iter_ldap_crypt_schemes())
 ldap_context = LazyCryptContext(_iter_ldap_schemes())
 
@@ -159,7 +159,7 @@
 # phpass & variants
 #=============================================================================
 def _create_phpass_policy(**kwds):
-    "helper to choose default alg based on bcrypt availability"
+    """helper to choose default alg based on bcrypt availability"""
     kwds['default'] = 'bcrypt' if hash.bcrypt.has_backend() else 'phpass'
     return kwds
 
--- a/MoinMoin/support/passlib/context.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/context.py	Mon Sep 05 23:44:04 2016 +0200
@@ -22,7 +22,7 @@
                           to_unicode, splitcomma
 from passlib.utils.compat import bytes, iteritems, num_types, \
                                  PY2, PY3, PY_MIN_32, unicode, SafeConfigParser, \
-                                 NativeStringIO, BytesIO, base_string_types
+                                 NativeStringIO, BytesIO, base_string_types, native_string_types
 # local
 __all__ = [
     'CryptContext',
@@ -40,7 +40,7 @@
 # TODO: merge the following helpers into _CryptConfig
 
 def _coerce_vary_rounds(value):
-    "parse vary_rounds string to percent as [0,1) float, or integer"
+    """parse vary_rounds string to percent as [0,1) float, or integer"""
     if value.endswith("%"):
         # XXX: deprecate this in favor of raw float?
         return float(value.rstrip("%"))*.01
@@ -77,7 +77,7 @@
     """
     .. deprecated:: 1.6
         This class has been deprecated, and will be removed in Passlib 1.8.
-        All of it's functionality has been rolled into :class:`CryptContext`.
+        All of its functionality has been rolled into :class:`CryptContext`.
 
     This class previously stored the configuration options for the
     CryptContext class. In the interest of interface simplification,
@@ -642,7 +642,7 @@
 
     @property
     def _errprefix(self):
-        "string used to identify record in error messages"
+        """string used to identify record in error messages"""
         handler = self.handler
         category = self.category
         if category:
@@ -657,7 +657,7 @@
     # rounds generation & limits - used by encrypt & deprecation code
     #===================================================================
     def _init_rounds_options(self, mn, mx, df, vr):
-        "parse options and compile efficient generate_rounds function"
+        """parse options and compile efficient generate_rounds function"""
         #----------------------------------------------------
         # extract hard limits from handler itself
         #----------------------------------------------------
@@ -669,7 +669,7 @@
         hmx = getattr(handler, "max_rounds", None)
 
         def check_against_handler(value, name):
-            "issue warning if value outside handler limits"
+            """issue warning if value outside handler limits"""
             if hmn is not None and value < hmn:
                 warn("%s: %s value is below handler minimum %d: %d" %
                      (self._errprefix, name, hmn, value), PasslibConfigWarning)
@@ -721,7 +721,7 @@
         # is calculated, so that proportion vr values are scaled against
         # the effective default.
         def clip(value):
-            "clip value to intersection of policy + handler limits"
+            """clip value to intersection of policy + handler limits"""
             if mn is not None and value < mn:
                 value = mn
             if hmn is not None and value < hmn:
@@ -799,7 +799,7 @@
     # encrypt() / genconfig()
     #===================================================================
     def _init_encrypt_and_genconfig(self):
-        "initialize genconfig/encrypt wrapper methods"
+        """initialize genconfig/encrypt wrapper methods"""
         settings = self.settings
         handler = self.handler
 
@@ -817,17 +817,17 @@
             self.encrypt = handler.encrypt
 
     def genconfig(self, **kwds):
-        "wrapper for handler.genconfig() which adds custom settings/rounds"
+        """wrapper for handler.genconfig() which adds custom settings/rounds"""
         self._prepare_settings(kwds)
         return self.handler.genconfig(**kwds)
 
     def encrypt(self, secret, **kwds):
-        "wrapper for handler.encrypt() which adds custom settings/rounds"
+        """wrapper for handler.encrypt() which adds custom settings/rounds"""
         self._prepare_settings(kwds)
         return self.handler.encrypt(secret, **kwds)
 
     def _prepare_settings(self, kwds):
-        "add default values to settings for encrypt & genconfig"
+        """add default values to settings for encrypt & genconfig"""
         # load in default values for any settings
         if kwds:
             for k,v in iteritems(self.settings):
@@ -869,7 +869,7 @@
     # of handler.verify()
 
     def _init_verify(self, mvt):
-        "initialize verify() wrapper - implements min_verify_time"
+        """initialize verify() wrapper - implements min_verify_time"""
         if mvt:
             assert isinstance(mvt, (int,float)) and mvt > 0, "CryptPolicy should catch this"
             self._min_verify_time = mvt
@@ -878,7 +878,7 @@
             self.verify = self.handler.verify
 
     def verify(self, secret, hash, **context):
-        "verify helper - adds min_verify_time delay"
+        """verify helper - adds min_verify_time delay"""
         mvt = self._min_verify_time
         assert mvt > 0, "wrapper should have been replaced for mvt=0"
         start = tick()
@@ -974,7 +974,7 @@
     """parses, validates, and stores CryptContext config
 
     this is a helper used internally by CryptContext to handle
-    parsing, validation, and serialization of it's config options.
+    parsing, validation, and serialization of its config options.
     split out from the main class, but not made public since
     that just complicates interface too much (c.f. CryptPolicy)
 
@@ -1024,7 +1024,7 @@
         """initialize .handlers and .schemes attributes"""
         handlers  = []
         schemes = []
-        if isinstance(data, str):
+        if isinstance(data, native_string_types):
             data = splitcomma(data)
         for elem in data or ():
             # resolve elem -> handler & scheme
@@ -1032,7 +1032,7 @@
                 handler = elem
                 scheme = handler.name
                 _validate_handler_name(scheme)
-            elif isinstance(elem, str):
+            elif isinstance(elem, native_string_types):
                 handler = get_crypt_handler(elem)
                 scheme = handler.name
             else:
@@ -1120,7 +1120,7 @@
             raise KeyError("%r option not allowed in CryptContext "
                            "configuration" % (key,))
         # coerce strings for certain fields (e.g. min_rounds uses ints)
-        if isinstance(value, str):
+        if isinstance(value, native_string_types):
             func = _coerce_scheme_options.get(key)
             if func:
                 value = func(value)
@@ -1131,12 +1131,12 @@
         if key == "default":
             if hasattr(value, "name"):
                 value = value.name
-            elif not isinstance(value, str):
+            elif not isinstance(value, native_string_types):
                 raise ExpectedTypeError(value, "str", "default")
             if schemes and value not in schemes:
                 raise KeyError("default scheme not found in policy")
         elif key == "deprecated":
-            if isinstance(value, str):
+            if isinstance(value, native_string_types):
                 value = splitcomma(value)
             elif not isinstance(value, (list,tuple)):
                 raise ExpectedTypeError(value, "str or seq", "deprecated")
@@ -1147,7 +1147,7 @@
             elif schemes:
                 # make sure list of deprecated schemes is subset of configured schemes
                 for scheme in value:
-                    if not isinstance(scheme, str):
+                    if not isinstance(scheme, native_string_types):
                         raise ExpectedTypeError(value, "str", "deprecated element")
                     if scheme not in schemes:
                         raise KeyError("deprecated scheme not found "
@@ -1167,7 +1167,8 @@
     #---------------------------------------------------------------
     def get_context_optionmap(self, key, _default={}):
         """return dict mapping category->value for specific context option.
-        (treat retval as readonly).
+
+        .. warning:: treat return value as readonly!
         """
         return self._context_options.get(key, _default)
 
@@ -1195,7 +1196,8 @@
     #---------------------------------------------------------------
     def _get_scheme_optionmap(self, scheme, category, default={}):
         """return all options for (scheme,category) combination
-        (treat return as readonly)
+
+        .. warning:: treat return value as readonly!
         """
         try:
             return self._scheme_options[scheme][category]
@@ -1281,7 +1283,7 @@
                                  "cannot be deprecated" % cat)
 
     def default_scheme(self, category):
-        "return default scheme for specific category"
+        """return default scheme for specific category"""
         defaults = self._default_schemes
         try:
             return defaults[category]
@@ -1293,7 +1295,7 @@
         return defaults[None]
 
     def is_deprecated_with_flag(self, scheme, category):
-        "is scheme deprecated under particular category?"
+        """is scheme deprecated under particular category?"""
         depmap = self.get_context_optionmap("deprecated")
         def test(cat):
             source = depmap.get(cat, depmap.get(None))
@@ -1339,7 +1341,7 @@
         """return composite dict of options for given scheme + category.
 
         this is currently a private method, though some variant
-        of it's output may eventually be made public.
+        of its output may eventually be made public.
 
         given a scheme & category, it returns two things:
         a set of all the keyword options to pass to the _CryptRecord constructor,
@@ -1370,7 +1372,7 @@
         return kwds, has_cat_options
 
     def get_record(self, scheme, category):
-        "return record for specific scheme & category (cached)"
+        """return record for specific scheme & category (cached)"""
         # NOTE: this is part of the critical path shared by
         #       all of CryptContext's PasswordHash methods,
         #       hence all the caching and error checking.
@@ -1382,12 +1384,12 @@
             pass
 
         # type check
-        if category is not None and not isinstance(category, str):
+        if category is not None and not isinstance(category, native_string_types):
             if PY2 and isinstance(category, unicode):
                 # for compatibility with unicode-centric py2 apps
                 return self.get_record(scheme, category.encode("utf-8"))
             raise ExpectedTypeError(category, "str or None", "category")
-        if scheme is not None and not isinstance(scheme, str):
+        if scheme is not None and not isinstance(scheme, native_string_types):
             raise ExpectedTypeError(scheme, "str or None", "scheme")
 
         # if scheme=None,
@@ -1550,7 +1552,7 @@
     #===================================================================
     @classmethod
     def _norm_source(cls, source):
-        "internal helper - accepts string, dict, or context"
+        """internal helper - accepts string, dict, or context"""
         if isinstance(source, dict):
             return cls(**source)
         elif isinstance(source, cls):
@@ -1669,7 +1671,7 @@
         return other
 
     def replace(self, **kwds):
-        "deprecated alias of :meth:`copy`"
+        """deprecated alias of :meth:`copy`"""
         warn("CryptContext().replace() has been deprecated in Passlib 1.6, "
              "and will be removed in Passlib 1.8, "
              "it has been renamed to CryptContext().copy()",
@@ -1752,7 +1754,7 @@
     #===================================================================
     @staticmethod
     def _parse_ini_stream(stream, section, filename):
-        "helper read INI from stream, extract passlib section as dict"
+        """helper read INI from stream, extract passlib section as dict"""
         # NOTE: this expects a unicode stream under py3,
         # and a utf-8 bytes stream under py2,
         # allowing the resulting dict to always use native strings.
@@ -1769,7 +1771,7 @@
 
         This function is a wrapper for :meth:`load` which
         loads a configuration string from the local file *path*,
-        instead of an in-memory source. It's behavior and options
+        instead of an in-memory source. Its behavior and options
         are otherwise identical to :meth:`!load` when provided with
         an INI-formatted string.
 
@@ -1812,7 +1814,7 @@
 
             * another :class:`!CryptContext` object.
 
-                this will export a snapshot of it's configuration
+                this will export a snapshot of its configuration
                 using :meth:`to_dict`.
 
         :type update: bool
@@ -1900,7 +1902,7 @@
     def _parse_config_key(ckey):
         """helper used to parse ``cat__scheme__option`` keys into a tuple"""
         # split string into 1-3 parts
-        assert isinstance(ckey, str)
+        assert isinstance(ckey, native_string_types)
         parts = ckey.replace(".","__").split("__")
         count = len(parts)
         if count == 1:
@@ -2019,7 +2021,7 @@
     #      and then decide whether to expose ability as deprecated_schemes(),
     #      is_deprecated(), or a just add a schemes(deprecated=True) flag.
     def _is_deprecated_scheme(self, scheme, category=None):
-        "helper used by unittests to check if scheme is deprecated"
+        """helper used by unittests to check if scheme is deprecated"""
         return self._get_record(scheme, category).deprecated
 
     def default_scheme(self, category=None, resolve=False):
@@ -2092,7 +2094,7 @@
                            "CryptContext instance")
 
     def _get_unregistered_handlers(self):
-        "check if any handlers in this context aren't in the global registry"
+        """check if any handlers in this context aren't in the global registry"""
         return tuple(handler for handler in self._config.handlers
                      if not _is_handler_registered(handler))
 
@@ -2101,7 +2103,7 @@
     #===================================================================
     @staticmethod
     def _render_config_key(key):
-        "convert 3-part config key to single string"
+        """convert 3-part config key to single string"""
         cat, scheme, option = key
         if cat:
             return "%s__%s__%s" % (cat, scheme or "context", option)
@@ -2112,7 +2114,7 @@
 
     @staticmethod
     def _render_ini_value(key, value):
-        "render value to string suitable for INI file"
+        """render value to string suitable for INI file"""
         # convert lists to comma separated lists
         # (mainly 'schemes' & 'deprecated')
         if isinstance(value, (list,tuple)):
@@ -2125,7 +2127,7 @@
             else:
                 value = str(value)
 
-        assert isinstance(value, str), \
+        assert isinstance(value, native_string_types), \
                "expected string for key: %r %r" % (key, value)
 
         # escape any percent signs.
@@ -2167,7 +2169,7 @@
                     for key, value in self._config.iter_config(resolve))
 
     def _write_to_parser(self, parser, section):
-        "helper to write to ConfigParser instance"
+        """helper to write to ConfigParser instance"""
         render_key = self._render_config_key
         render_value = self._render_ini_value
         parser.add_section(section)
@@ -2240,7 +2242,7 @@
     #       which are optimized for the specific (scheme,category) configuration.
     #
     #       The record objects are cached inside the _CryptConfig
-    #       instance stored in self._config, and are retreived
+    #       instance stored in self._config, and are retrieved
     #       via get_record() and identify_record().
     #
     #       _get_record() and _identify_record() are references
@@ -2248,7 +2250,7 @@
     #       stored in CryptContext for speed.
 
     def _get_or_identify_record(self, hash, scheme=None, category=None):
-        "return record based on scheme, or failing that, by identifying hash"
+        """return record based on scheme, or failing that, by identifying hash"""
         if scheme:
             if not isinstance(hash, base_string_types):
                 raise ExpectedStringError(hash, "hash")
@@ -2354,7 +2356,7 @@
 
         :param \*\*settings:
             All additional keywords are passed to the appropriate handler,
-            and should match it's :attr:`~passlib.ifc.PasswordHash.setting_kwds`.
+            and should match its :attr:`~passlib.ifc.PasswordHash.setting_kwds`.
 
         :returns:
             A configuration string suitable for passing to :meth:`~CryptContext.genhash`,
@@ -2398,7 +2400,7 @@
 
         :param \*\*kwds:
             All additional keywords are passed to the appropriate handler,
-            and should match it's :attr:`~passlib.ifc.PasswordHash.context_kwds`.
+            and should match its :attr:`~passlib.ifc.PasswordHash.context_kwds`.
 
         :returns:
             The secret as encoded by the specified algorithm and options.
@@ -2526,7 +2528,7 @@
 
         :param \*\*kwds:
             All additional keywords are passed to the appropriate handler,
-            and should match it's :attr:`~passlib.ifc.PasswordHash.context_kwds`.
+            and should match its :attr:`~passlib.ifc.PasswordHash.context_kwds`.
 
         :returns:
             ``True`` if the password matched the hash, else ``False``.
@@ -2627,9 +2629,9 @@
     """CryptContext subclass which doesn't load handlers until needed.
 
     This is a subclass of CryptContext which takes in a set of arguments
-    exactly like CryptContext, but won't load any handlers
-    (or even parse it's arguments) until
-    the first time one of it's methods is accessed.
+    exactly like CryptContext, but won't import any handlers
+    (or even parse its arguments) until
+    the first time one of its methods is accessed.
 
     :arg schemes:
         The first positional argument can be a list of schemes, or omitted,
@@ -2666,6 +2668,12 @@
     but using :func:`!onload()` to provide dynamic configuration
     at *application-run* time.
 
+    .. note:: 
+        This class is only useful if you're referencing handler objects by name,
+        and don't want them imported until runtime. If you want to have the config
+        validated before your application runs, or are passing in already-imported
+        handler instances, you should use :class:`CryptContext` instead.
+
     .. versionadded:: 1.4
     """
     _lazy_kwds = None
--- a/MoinMoin/support/passlib/exc.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/exc.py	Mon Sep 05 23:44:04 2016 +0200
@@ -39,6 +39,15 @@
     # this also prevents a glibc crypt segfault issue, detailed here ...
     # http://www.openwall.com/lists/oss-security/2011/11/15/1
 
+
+class PasslibSecurityError(RuntimeError):
+    """
+    Error raised if critical security issue is detected
+    (e.g. an attempt is made to use a vulnerable version of a bcrypt backend).
+
+    .. versionadded:: 1.6.3
+    """
+
 #=============================================================================
 # warnings
 #=============================================================================
@@ -86,7 +95,7 @@
     """Warning issued when something unexpected happens during runtime.
 
     The fact that it's a warning instead of an error means Passlib
-    was able to correct for the issue, but that it's anonmalous enough
+    was able to correct for the issue, but that it's anomalous enough
     that the developers would love to hear under what conditions it occurred.
 
     .. versionadded:: 1.6
@@ -116,7 +125,7 @@
 # generic helpers
 #------------------------------------------------------------------------
 def type_name(value):
-    "return pretty-printed string containing name of value's type"
+    """return pretty-printed string containing name of value's type"""
     cls = value.__class__
     if cls.__module__ and cls.__module__ not in ["__builtin__", "builtins"]:
         return "%s.%s" % (cls.__module__, cls.__name__)
@@ -126,26 +135,26 @@
         return cls.__name__
 
 def ExpectedTypeError(value, expected, param):
-    "error message when param was supposed to be one type, but found another"
+    """error message when param was supposed to be one type, but found another"""
     # NOTE: value is never displayed, since it may sometimes be a password.
     name = type_name(value)
     return TypeError("%s must be %s, not %s" % (param, expected, name))
 
 def ExpectedStringError(value, param):
-    "error message when param was supposed to be unicode or bytes"
+    """error message when param was supposed to be unicode or bytes"""
     return ExpectedTypeError(value, "unicode or bytes", param)
 
 #------------------------------------------------------------------------
 # encrypt/verify parameter errors
 #------------------------------------------------------------------------
 def MissingDigestError(handler=None):
-    "raised when verify() method gets passed config string instead of hash"
+    """raised when verify() method gets passed config string instead of hash"""
     name = _get_name(handler)
     return ValueError("expected %s hash, got %s config string instead" %
                      (name, name))
 
 def NullPasswordError(handler=None):
-    "raised by OS crypt() supporting hashes, which forbid NULLs in password"
+    """raised by OS crypt() supporting hashes, which forbid NULLs in password"""
     name = _get_name(handler)
     return ValueError("%s does not allow NULL bytes in password" % name)
 
@@ -153,25 +162,25 @@
 # errors when parsing hashes
 #------------------------------------------------------------------------
 def InvalidHashError(handler=None):
-    "error raised if unrecognized hash provided to handler"
+    """error raised if unrecognized hash provided to handler"""
     return ValueError("not a valid %s hash" % _get_name(handler))
 
 def MalformedHashError(handler=None, reason=None):
-    "error raised if recognized-but-malformed hash provided to handler"
+    """error raised if recognized-but-malformed hash provided to handler"""
     text = "malformed %s hash" % _get_name(handler)
     if reason:
         text = "%s (%s)" % (text, reason)
     return ValueError(text)
 
 def ZeroPaddedRoundsError(handler=None):
-    "error raised if hash was recognized but contained zero-padded rounds field"
+    """error raised if hash was recognized but contained zero-padded rounds field"""
     return MalformedHashError(handler, "zero-padded rounds")
 
 #------------------------------------------------------------------------
 # settings / hash component errors
 #------------------------------------------------------------------------
 def ChecksumSizeError(handler, raw=False):
-    "error raised if hash was recognized, but checksum was wrong size"
+    """error raised if hash was recognized, but checksum was wrong size"""
     # TODO: if handler.use_defaults is set, this came from app-provided value,
     # not from parsing a hash string, might want different error msg.
     checksum_size = handler.checksum_size
--- a/MoinMoin/support/passlib/ext/django/models.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/ext/django/models.py	Mon Sep 05 23:44:04 2016 +0200
@@ -61,7 +61,7 @@
     FORMS_PATH = "django.contrib.auth.forms"
 
     #
-    # import UNUSUABLE_PASSWORD and is_password_usuable() helpers
+    # import UNUSABLE_PASSWORD and is_password_usable() helpers
     # (providing stubs for older django versions)
     #
     if VERSION < (1,4):
@@ -72,7 +72,7 @@
             from django.contrib.auth.models import UNUSABLE_PASSWORD
 
         def is_password_usable(encoded):
-            return encoded is not None and encoded != UNUSABLE_PASSWORD
+            return (encoded is not None and encoded != UNUSABLE_PASSWORD)
 
         def is_valid_secret(secret):
             return secret is not None
@@ -128,7 +128,7 @@
     #
     @_manager.monkeypatch(USER_PATH)
     def set_password(user, password):
-        "passlib replacement for User.set_password()"
+        """passlib replacement for User.set_password()"""
         if is_valid_secret(password):
             # NOTE: pulls _get_category from module globals
             cat = _get_category(user)
@@ -138,7 +138,7 @@
 
     @_manager.monkeypatch(USER_PATH)
     def check_password(user, password):
-        "passlib replacement for User.check_password()"
+        """passlib replacement for User.check_password()"""
         hash = user.password
         if not is_valid_secret(password) or not is_password_usable(hash):
             return False
@@ -160,8 +160,8 @@
     @_manager.monkeypatch(HASHERS_PATH, enable=has_hashers)
     @_manager.monkeypatch(MODELS_PATH)
     def check_password(password, encoded, setter=None, preferred="default"):
-        "passlib replacement for check_password()"
-        # XXX: this currently ignores "preferred" keyword, since it's purpose
+        """passlib replacement for check_password()"""
+        # XXX: this currently ignores "preferred" keyword, since its purpose
         #      was for hash migration, and that's handled by the context.
         if not is_valid_secret(password) or not is_password_usable(encoded):
             return False
@@ -178,7 +178,7 @@
         @_manager.monkeypatch(HASHERS_PATH)
         @_manager.monkeypatch(MODELS_PATH)
         def make_password(password, salt=None, hasher="default"):
-            "passlib replacement for make_password()"
+            """passlib replacement for make_password()"""
             if not is_valid_secret(password):
                 return make_unusable_password()
             if hasher == "default":
@@ -187,17 +187,22 @@
                 scheme = hasher_to_passlib_name(hasher)
             kwds = dict(scheme=scheme)
             handler = password_context.handler(scheme)
-            # NOTE: django make specify an empty string for the salt,
-            #       even if scheme doesn't accept a salt. we omit keyword
-            #       in that case.
-            if salt is not None and (salt or 'salt' in handler.setting_kwds):
-                kwds['salt'] = salt
+            if "salt" in handler.setting_kwds:
+                if hasher.startswith("unsalted_"):
+                    # Django 1.4.6+ uses a separate 'unsalted_sha1' hasher for "sha1$$digest",
+                    # but passlib just reuses it's "sha1" handler ("sha1$salt$digest"). To make
+                    # this work, have to explicitly tell the sha1 handler to use an empty salt.
+                    kwds['salt'] = ''
+                elif salt:
+                    # Django make_password() autogenerates a salt if salt is bool False (None / ''),
+                    # so we only pass the keyword on if there's actually a fixed salt.
+                    kwds['salt'] = salt
             return password_context.encrypt(password, **kwds)
 
         @_manager.monkeypatch(HASHERS_PATH)
         @_manager.monkeypatch(FORMS_PATH)
         def get_hasher(algorithm="default"):
-            "passlib replacement for get_hasher()"
+            """passlib replacement for get_hasher()"""
             if algorithm == "default":
                 scheme = None
             else:
@@ -214,7 +219,7 @@
         @_manager.monkeypatch(HASHERS_PATH)
         @_manager.monkeypatch(FORMS_PATH)
         def identify_hasher(encoded):
-            "passlib helper to identify hasher from encoded password"
+            """passlib helper to identify hasher from encoded password"""
             handler = password_context.identify(encoded, resolve=True,
                                                 required=True)
             algorithm = None
--- a/MoinMoin/support/passlib/ext/django/utils.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/ext/django/utils.py	Mon Sep 05 23:44:04 2016 +0200
@@ -123,14 +123,14 @@
 _other_django_hashes = ["hex_md5"]
 
 def passlib_to_hasher_name(passlib_name):
-    "convert passlib handler name -> hasher name"
+    """convert passlib handler name -> hasher name"""
     handler = get_crypt_handler(passlib_name)
     if hasattr(handler, "django_name"):
         return handler.django_name
     return PASSLIB_HASHER_PREFIX + passlib_name
 
 def hasher_to_passlib_name(hasher_name):
-    "convert hasher name -> passlib handler name"
+    """convert hasher name -> passlib handler name"""
     if hasher_name.startswith(PASSLIB_HASHER_PREFIX):
         return hasher_name[len(PASSLIB_HASHER_PREFIX):]
     if hasher_name == "unsalted_sha1":
@@ -186,7 +186,9 @@
     _translate_kwds = dict(checksum="hash", rounds="iterations")
 
     def safe_summary(self, encoded):
-        from django.contrib.auth.hashers import mask_hash, _, SortedDict
+        from django.contrib.auth.hashers import mask_hash
+        from django.utils.translation import ugettext_noop as _
+        from django.utils.datastructures import SortedDict
         handler = self.passlib_handler
         items = [
             # since this is user-facing, we're reporting passlib's name,
@@ -252,14 +254,14 @@
         return hasher
 
 def _get_hasher(algorithm):
-    "wrapper to call django.contrib.auth.hashers:get_hasher()"
+    """wrapper to call django.contrib.auth.hashers:get_hasher()"""
     import sys
     module = sys.modules.get("passlib.ext.django.models")
     if module is None:
         # we haven't patched django, so just import directly
         from django.contrib.auth.hashers import get_hasher
     else:
-        # we've patched django, so have to use patch manager to retreive
+        # we've patched django, so have to use patch manager to retrieve
         # original get_hasher() function...
         get_hasher = module._manager.getorig("django.contrib.auth.hashers:get_hasher")
     return get_hasher(algorithm)
@@ -364,7 +366,7 @@
 _UNSET = object()
 
 class _PatchManager(object):
-    "helper to manage monkeypatches and run sanity checks"
+    """helper to manage monkeypatches and run sanity checks"""
 
     # NOTE: this could easily use a dict interface,
     #       but keeping it distinct to make clear that it's not a dict,
@@ -383,7 +385,7 @@
     __bool__ = __nonzero__ = lambda self: bool(self._state)
 
     def _import_path(self, path):
-        "retrieve obj and final attribute name from resource path"
+        """retrieve obj and final attribute name from resource path"""
         name, attr = path.split(":")
         obj = __import__(name, fromlist=[attr], level=0)
         while '.' in attr:
@@ -393,7 +395,7 @@
 
     @staticmethod
     def _is_same_value(left, right):
-        "check if two values are the same (stripping method wrappers, etc)"
+        """check if two values are the same (stripping method wrappers, etc)"""
         return get_method_function(left) == get_method_function(right)
 
     #===================================================================
@@ -404,11 +406,11 @@
         return getattr(obj, attr, default)
 
     def get(self, path, default=None):
-        "return current value for path"
+        """return current value for path"""
         return self._get_path(path, default)
 
     def getorig(self, path, default=None):
-        "return original (unpatched) value for path"
+        """return original (unpatched) value for path"""
         try:
             value, _= self._state[path]
         except KeyError:
@@ -439,7 +441,7 @@
             setattr(obj, attr, value)
 
     def patch(self, path, value):
-        "monkeypatch object+attr at <path> to have <value>, stores original"
+        """monkeypatch object+attr at <path> to have <value>, stores original"""
         assert value != _UNSET
         current = self._get_path(path)
         try:
@@ -461,7 +463,7 @@
     ##        self.patch(path, value)
 
     def monkeypatch(self, parent, name=None, enable=True):
-        "function decorator which patches function of same name in <parent>"
+        """function decorator which patches function of same name in <parent>"""
         def builder(func):
             if enable:
                 sep = "." if ":" in parent else ":"
--- a/MoinMoin/support/passlib/handlers/bcrypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/bcrypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -24,11 +24,11 @@
 except ImportError: # pragma: no cover
     _bcrypt = None
 try:
-    from bcryptor.engine import Engine as bcryptor_engine
+    import bcryptor as _bcryptor
 except ImportError: # pragma: no cover
-    bcryptor_engine = None
+    _bcryptor = None
 # pkg
-from passlib.exc import PasslibHashWarning
+from passlib.exc import PasslibHashWarning, PasslibSecurityWarning, PasslibSecurityError
 from passlib.utils import bcrypt64, safe_crypt, repeat_string, to_bytes, \
                           classproperty, rng, getrandstr, test_crypt, to_unicode
 from passlib.utils.compat import bytes, b, u, uascii_to_str, unicode, str_to_uascii
@@ -53,8 +53,43 @@
 IDENT_2A = u("$2a$")
 IDENT_2X = u("$2x$")
 IDENT_2Y = u("$2y$")
+IDENT_2B = u("$2b$")
 _BNULL = b('\x00')
 
+def _detect_pybcrypt():
+    """
+    internal helper which tries to distinguish pybcrypt vs bcrypt.
+
+    :returns:
+        True if cext-based py-bcrypt,
+        False if ffi-based bcrypt,
+        None if 'bcrypt' module not found.
+
+    .. versionchanged:: 1.6.3
+
+        Now assuming bcrypt installed, unless py-bcrypt explicitly detected.
+        Previous releases assumed py-bcrypt by default.
+
+        Making this change since py-bcrypt is (apparently) unmaintained and static,
+        whereas bcrypt is being actively maintained, and it's internal structure may shift.
+    """
+    # NOTE: this is also used by the unittests.
+
+    # check for module.
+    try:
+        import bcrypt
+    except ImportError:
+        return None
+
+    # py-bcrypt has a "._bcrypt.__version__" attribute (confirmed for v0.1 - 0.4),
+    # which bcrypt lacks (confirmed for v1.0 - 2.0)
+    # "._bcrypt" alone isn't sufficient, since bcrypt 2.0 now has that attribute.
+    try:
+        from bcrypt._bcrypt import __version__
+    except ImportError:
+        return False
+    return True
+
 #=============================================================================
 # handler
 #=============================================================================
@@ -85,9 +120,11 @@
         If specified, it must be one of the following:
 
         * ``"2"`` - the first revision of BCrypt, which suffers from a minor security flaw and is generally not used anymore.
-        * ``"2a"`` - latest revision of the official BCrypt algorithm, and the current default.
+        * ``"2a"`` - some implementations suffered from a very rare security flaw.
+          current default for compatibility purposes.
         * ``"2y"`` - format specific to the *crypt_blowfish* BCrypt implementation,
           identical to ``"2a"`` in all but name.
+        * ``"2b"`` - latest revision of the official BCrypt algorithm (will be default in Passlib 1.7).
 
     :type relaxed: bool
     :param relaxed:
@@ -107,6 +144,10 @@
 
     .. versionchanged:: 1.6
         Added a pure-python backend.
+
+    .. versionchanged:: 1.6.3
+
+        Added support for ``"2b"`` variant.
     """
 
     #===================================================================
@@ -120,8 +161,9 @@
 
     #--HasManyIdents--
     default_ident = IDENT_2A
-    ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y)
-    ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A,  u("2y"): IDENT_2Y}
+    ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y, IDENT_2B)
+    ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A,  u("2y"): IDENT_2Y,
+                     u("2b"): IDENT_2B}
 
     #--HasSalt--
     min_salt_size = max_salt_size = 22
@@ -161,19 +203,10 @@
                                    self.checksum or u(''))
         return uascii_to_str(hash)
 
-    def _get_config(self, ident=None):
-        "internal helper to prepare config string for backends"
-        if ident is None:
-            ident = self.ident
-        if ident == IDENT_2Y:
-            # none of passlib's backends suffered from crypt_blowfish's
-            # buggy "2a" hash, which means we can safely implement
-            # crypt_blowfish's "2y" hash by passing "2a" to the backends.
-            ident = IDENT_2A
-        else:
-            # no backends currently support 2x, but that should have
-            # been caught earlier in from_string()
-            assert ident != IDENT_2X
+    # NOTE: this should be kept separate from to_string()
+    #       so that bcrypt_sha256() can still use it, while overriding to_string()
+    def _get_config(self, ident):
+        """internal helper to prepare config string for backends"""
         config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
         return uascii_to_str(config)
 
@@ -197,7 +230,7 @@
 
     @classmethod
     def normhash(cls, hash):
-        "helper to normalize hash, correcting any bcrypt padding bits"
+        """helper to normalize hash, correcting any bcrypt padding bits"""
         if cls.identify(hash):
             return cls.from_string(hash).to_string()
         else:
@@ -241,17 +274,100 @@
     #===================================================================
     backends = ("bcrypt", "pybcrypt", "bcryptor", "os_crypt", "builtin")
 
+    # backend workaround detection
+    _has_wraparound_bug = False
+    _lacks_20_support = False
+    _lacks_2y_support = False
+    _lacks_2b_support = False
+
+    @classmethod
+    def set_backend(cls, *a, **k):
+        backend = super(bcrypt, cls).set_backend(*a, **k)
+        cls._scan_backend(backend)
+        return backend
+
+    @classmethod
+    def _scan_backend(cls, backend):
+        """
+        check for known bugs & feature support once backend is loaded
+        """
+        # check for cryptblowfish 8bit bug (fixed in 2y/2b);
+        # even though it's not known to be present in any of passlib's backends.
+        # this is treated as FATAL, because it can easily result in seriously malformed hashes,
+        # and we can't correct for it ourselves.
+        # test cases from <http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/glibc/crypt_blowfish/wrapper.c.diff?r1=1.9;r2=1.10>
+        # NOTE: reference hash taken from above url, and is the incorrectly generate 2x hash.
+        if cls.verify(u("\xA3"),
+                      "$2a$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"):
+            raise PasslibSecurityError(
+                "passlib.hash.bcrypt: Your installation of the %r backend is vulnerable to "
+                "the crypt_blowfish 8-bit bug (CVE-2011-2483), "
+                "and should be upgraded or replaced with another backend." % backend)
+
+        # check for bsd wraparound bug (fixed in 2b)
+        # this is treated as a warning, because it's rare in the field,
+        # and pybcrypt (as of 2015-7-21) is unpatched, but some people may be stuck with it.
+        # test cases from <http://www.openwall.com/lists/oss-security/2012/01/02/4>
+        # NOTE: reference hash is of password "0"*72
+        # NOTE: if in future we need to deliberately create hashes which have this bug,
+        #       can use something like 'hashpw(repeat_string(secret[:((1+secret) % 256) or 1]), 72)'
+        cls._has_wraparound_bug = False
+        if cls.verify(("0123456789"*26)[:255],
+                      "$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"):
+            warn("passlib.hash.bcrypt: Your installation of the %r backend is vulnerable to "
+                 "the bsd wraparound bug, "
+                 "and should be upgraded or replaced with another backend "
+                 "(this warning will be fatal under passlib 1.7)" % backend)
+            cls._has_wraparound_bug = True
+
+        def _detect_lacks_variant(ident, refhash):
+            """helper to detect if backend *lacks* support for specified bcrypt variant"""
+            assert refhash.startswith(ident)
+            # NOTE: can't use cls.verify() directly or we have recursion error
+            try:
+                result = cls.verify("test", refhash)
+            except (ValueError, _bcryptor.engine.SaltError if _bcryptor else ValueError):
+                # backends without support will throw various errors about unrecognized version
+                # pybcrypt, bcrypt -- raises ValueError
+                # bcryptor -- raises bcryptor.engine.SaltError
+                log.debug("%r backend lacks %r support", backend, ident)
+                return True
+            assert result, "%r backend %r check failed" % (backend, ident)
+            return False
+
+        # check for native 2 support
+        # NOTE: have to clear workaround first, so verify() doesn't enable it during detection.
+        cls._lacks_20_support = False
+        cls._lacks_20_support = _detect_lacks_variant("$2$", "$2$04$5BJqKfqMQvV7nS.yUguNcu"
+                                                             "RfMMOXK0xPWavM7pOzjEi5ze5T1k8/S")
+
+        # TODO: check for 2x support
+
+        # check for native 2y support
+        cls._lacks_2y_support = False
+        cls._lacks_2y_support = _detect_lacks_variant("$2y$", "$2y$04$5BJqKfqMQvV7nS.yUguNcu"
+                                                              "eVirQqDBGaLXSqj.rs.pZPlNR0UX/HK")
+
+        # check for native 2b support
+        cls._lacks_2b_support = False
+        cls._lacks_2b_support = _detect_lacks_variant("$2b$", "$2b$04$5BJqKfqMQvV7nS.yUguNcu"
+                                                              "eVirQqDBGaLXSqj.rs.pZPlNR0UX/HK")
+
+        # sanity check
+        assert cls._lacks_2b_support or not cls._has_wraparound_bug, \
+            "sanity check failed: %r backend supports $2b$ but has wraparound bug" % backend
+
     @classproperty
     def _has_backend_bcrypt(cls):
-        return _bcrypt is not None and hasattr(_bcrypt, "_ffi")
+        return _bcrypt is not None and not _detect_pybcrypt()
 
     @classproperty
     def _has_backend_pybcrypt(cls):
-        return _bcrypt is not None and not hasattr(_bcrypt, "_ffi")
+        return _bcrypt is not None and _detect_pybcrypt()
 
     @classproperty
     def _has_backend_bcryptor(cls):
-        return bcryptor_engine is not None
+        return _bcryptor is not None
 
     @classproperty
     def _has_backend_builtin(cls):
@@ -271,51 +387,93 @@
 
     @classmethod
     def _no_backends_msg(cls):
-        return "no bcrypt backends available - please install py-bcrypt"
+        return "no bcrypt backends available -- recommend you install one (e.g. 'pip install bcrypt')"
 
     def _calc_checksum(self, secret):
-        "common backend code"
+        """common backend code"""
+
+        # make sure it's unicode
         if isinstance(secret, unicode):
             secret = secret.encode("utf-8")
+
+        # NOTE: especially important to forbid NULLs for bcrypt, since many
+        # backends (bcryptor, bcrypt) happily accept them, and then
+        # silently truncate the password at first NULL they encounter!
         if _BNULL in secret:
-            # NOTE: especially important to forbid NULLs for bcrypt, since many
-            # backends (bcryptor, bcrypt) happily accept them, and then
-            # silently truncate the password at first NULL they encounter!
             raise uh.exc.NullPasswordError(self)
-        return self._calc_checksum_backend(secret)
 
-    def _calc_checksum_os_crypt(self, secret):
-        config = self._get_config()
+        # ensure backend is loaded before workaround detection
+        self.get_backend()
+
+        # protect from wraparound bug by truncating secret before handing it to the backend.
+        # bcrypt only uses first 72 bytes anyways.
+        if self._has_wraparound_bug and len(secret) >= 255:
+            secret = secret[:72]
+
+        # special case handling for variants (ordered most common first)
+        ident = self.ident
+        if ident == IDENT_2A:
+            # fall through and use backend w/o hacks
+            pass
+
+        elif ident == IDENT_2B:
+            if self._lacks_2b_support:
+                # handle $2b$ hash format even if backend is too old.
+                # have it generate a 2A digest, then return it as a 2B hash.
+                ident = IDENT_2A
+
+        elif ident == IDENT_2Y:
+            if self._lacks_2y_support:
+                # handle $2y$ hash format (not supported by BSDs, being phased out on others)
+                # have it generate a 2A digest, then return it as a 2Y hash.
+                ident = IDENT_2A
+
+        elif ident == IDENT_2:
+            if self._lacks_20_support:
+                # handle legacy $2$ format (not supported by most backends except BSD os_crypt)
+                # we can fake $2$ behavior using the $2a$ algorithm
+                # by repeating the password until it's at least 72 chars in length.
+                if secret:
+                    secret = repeat_string(secret, 72)
+                ident = IDENT_2A
+
+        elif ident == IDENT_2X:
+
+            # NOTE: shouldn't get here.
+            # XXX: could check if backend does actually offer 'support'
+            raise RuntimeError("$2x$ hashes not currently supported by passlib")
+
+        else:
+            raise AssertionError("unexpected ident value: %r" % ident)
+
+        # invoke backend
+        config = self._get_config(ident)
+        return self._calc_checksum_backend(secret, config)
+
+    def _calc_checksum_os_crypt(self, secret, config):
         hash = safe_crypt(secret, config)
         if hash:
             assert hash.startswith(config) and len(hash) == len(config)+31
             return hash[-31:]
         else:
-            # NOTE: it's unlikely any other backend will be available,
-            # but checking before we bail, just in case.
-            for name in self.backends:
-                if name != "os_crypt" and self.has_backend(name):
-                    func = getattr(self, "_calc_checksum_" + name)
-                    return func(secret)
+            # NOTE: Have to raise this error because python3's crypt.crypt() only accepts unicode.
+            #       This means it can't handle any passwords that aren't either unicode
+            #       or utf-8 encoded bytes.  However, hashing a password with an alternate
+            #       encoding should be a pretty rare edge case; if user needs it, they can just
+            #       install bcrypt backend.
+            # XXX: is this the right error type to raise?
+            #      maybe have safe_crypt() not swallow UnicodeDecodeError, and have handlers
+            #      like sha256_crypt trap it if they have alternate method of handling them?
             raise uh.exc.MissingBackendError(
-                "password can't be handled by os_crypt, "
-                "recommend installing py-bcrypt.",
+                "non-utf8 encoded passwords can't be handled by crypt.crypt() under python3, "
+                "recommend running `pip install bcrypt`.",
                 )
 
-    def _calc_checksum_bcrypt(self, secret):
+    def _calc_checksum_bcrypt(self, secret, config):
         # bcrypt behavior:
         #   hash must be ascii bytes
         #   secret must be bytes
         #   returns bytes
-        if self.ident == IDENT_2:
-            # bcrypt doesn't support $2$ hashes; but we can fake $2$ behavior
-            # using the $2a$ algorithm, by repeating the password until
-            # it's at least 72 chars in length.
-            if secret:
-                secret = repeat_string(secret, 72)
-            config = self._get_config(IDENT_2A)
-        else:
-            config = self._get_config()
         if isinstance(config, unicode):
             config = config.encode("ascii")
         hash = _bcrypt.hashpw(secret, config)
@@ -323,37 +481,27 @@
         assert isinstance(hash, bytes)
         return hash[-31:].decode("ascii")
 
-    def _calc_checksum_pybcrypt(self, secret):
+    def _calc_checksum_pybcrypt(self, secret, config):
         # py-bcrypt behavior:
         #   py2: unicode secret/hash encoded as ascii bytes before use,
         #        bytes taken as-is; returns ascii bytes.
         #   py3: unicode secret encoded as utf-8 bytes,
         #        hash encoded as ascii bytes, returns ascii unicode.
-        config = self._get_config()
         hash = _bcrypt.hashpw(secret, config)
         assert hash.startswith(config) and len(hash) == len(config)+31
         return str_to_uascii(hash[-31:])
 
-    def _calc_checksum_bcryptor(self, secret):
+    def _calc_checksum_bcryptor(self, secret, config):
         # bcryptor behavior:
         #   py2: unicode secret/hash encoded as ascii bytes before use,
         #        bytes taken as-is; returns ascii bytes.
         #   py3: not supported
-        if self.ident == IDENT_2:
-            # bcryptor doesn't support $2$ hashes; but we can fake $2$ behavior
-            # using the $2a$ algorithm, by repeating the password until
-            # it's at least 72 chars in length.
-            if secret:
-                secret = repeat_string(secret, 72)
-            config = self._get_config(IDENT_2A)
-        else:
-            config = self._get_config()
-        hash = bcryptor_engine(False).hash_key(secret, config)
+        hash = _bcryptor.engine.Engine(False).hash_key(secret, config)
         assert hash.startswith(config) and len(hash) == len(config)+31
         return str_to_uascii(hash[-31:])
 
-    def _calc_checksum_builtin(self, secret):
-        chk = _builtin_bcrypt(secret, self.ident.strip("$"),
+    def _calc_checksum_builtin(self, secret, config):
+        chk = _builtin_bcrypt(secret, config[1:config.index("$", 1)],
                               self.salt.encode("ascii"), self.rounds)
         return chk.decode("ascii")
 
@@ -435,19 +583,21 @@
         return uascii_to_str(hash)
 
     def _calc_checksum(self, secret):
-        # NOTE: this bypasses bcrypt's _calc_checksum,
-        #       so has to take care of all it's issues, such as secret encoding.
-        if isinstance(secret, unicode):
-            secret = secret.encode("utf-8")
         # NOTE: can't use digest directly, since bcrypt stops at first NULL.
         # NOTE: bcrypt doesn't fully mix entropy for bytes 55-72 of password
         #       (XXX: citation needed), so we don't want key to be > 55 bytes.
         #       thus, have to use base64 (44 bytes) rather than hex (64 bytes).
+        # XXX: it's later come out that 55-72 may be ok, so later revision of bcrypt_sha256
+        #      may switch to hex encoding, since it's simpler to implement elsewhere.
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
         key = b64encode(sha256(secret).digest())
-        return self._calc_checksum_backend(key)
+
+        # hand result off to normal bcrypt algorithm
+        return super(bcrypt_sha256, self)._calc_checksum(key)
 
     # patch set_backend so it modifies bcrypt class, not this one...
-    # else it would clobber our _calc_checksum() wrapper above.
+    # else the bcrypt.set_backend() tests will call the wrong class.
     @classmethod
     def set_backend(cls, *args, **kwds):
         return bcrypt.set_backend(*args, **kwds)
--- a/MoinMoin/support/passlib/handlers/cisco.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/cisco.py	Mon Sep 05 23:44:04 2016 +0200
@@ -109,7 +109,7 @@
         will be issued instead. Correctable errors include
         ``salt`` values that are out of range.
 
-    Note that while this class outputs digests in upper-case hexidecimal,
+    Note that while this class outputs digests in upper-case hexadecimal,
     it will accept lower-case as well.
 
     This class also provides the following additional method:
@@ -156,7 +156,7 @@
         self.salt = self._norm_salt(salt)
 
     def _norm_salt(self, salt):
-        "the salt for this algorithm is an integer 0-52, not a string"
+        """the salt for this algorithm is an integer 0-52, not a string"""
         # XXX: not entirely sure that values >15 are valid, so for
         # compatibility we don't output those values, but we do accept them.
         if salt is None:
@@ -206,7 +206,7 @@
 
     @classmethod
     def _cipher(cls, data, salt):
-        "xor static key against data - encrypts & decrypts"
+        """xor static key against data - encrypts & decrypts"""
         key = cls._key
         key_size = len(key)
         return join_byte_values(
--- a/MoinMoin/support/passlib/handlers/des_crypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/des_crypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -40,7 +40,7 @@
                for i, c in enumerate(secret[:8]))
 
 def _raw_des_crypt(secret, salt):
-    "pure-python backed for des_crypt"
+    """pure-python backed for des_crypt"""
     assert len(salt) == 2
 
     # NOTE: some OSes will accept non-HASH64 characters in the salt,
@@ -73,7 +73,7 @@
     return h64big.encode_int64(result)
 
 def _bsdi_secret_to_key(secret):
-    "covert secret to DES key used by bsdi_crypt"
+    """covert secret to DES key used by bsdi_crypt"""
     key_value = _crypt_secret_to_key(secret)
     idx = 8
     end = len(secret)
@@ -85,7 +85,7 @@
     return key_value
 
 def _raw_bsdi_crypt(secret, rounds, salt):
-    "pure-python backend for bsdi_crypt"
+    """pure-python backend for bsdi_crypt"""
 
     # decode salt
     try:
--- a/MoinMoin/support/passlib/handlers/digests.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/digests.py	Mon Sep 05 23:44:04 2016 +0200
@@ -24,10 +24,10 @@
 ]
 
 #=============================================================================
-# helpers for hexidecimal hashes
+# helpers for hexadecimal hashes
 #=============================================================================
 class HexDigestHash(uh.StaticHandler):
-    "this provides a template for supporting passwords stored as plain hexidecimal hashes"
+    """this provides a template for supporting passwords stored as plain hexadecimal hashes"""
     #===================================================================
     # class attrs
     #===================================================================
@@ -60,7 +60,7 @@
         __module__=module, # so ABCMeta won't clobber it
         _hash_func=staticmethod(hash), # sometimes it's a function, sometimes not. so wrap it.
         checksum_size=h.digest_size*2,
-        __doc__="""This class implements a plain hexidecimal %s hash, and follows the :ref:`password-hash-api`.
+        __doc__="""This class implements a plain hexadecimal %s hash, and follows the :ref:`password-hash-api`.
 
 It supports no optional or contextual keywords.
 """ % (digest_name,)
@@ -106,7 +106,7 @@
 
     @classmethod
     def _norm_hash(cls, hash):
-        "normalize hash to native string, and validate it"
+        """normalize hash to native string, and validate it"""
         hash = to_native_str(hash, param="hash")
         if len(hash) != 32:
             raise uh.exc.MalformedHashError(cls, "wrong size")
--- a/MoinMoin/support/passlib/handlers/django.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/django.py	Mon Sep 05 23:44:04 2016 +0200
@@ -270,7 +270,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 20000, but must be within ``range(1,1<<32)``.
+        Defaults to 29000, but must be within ``range(1,1<<32)``.
 
     :type relaxed: bool
     :param relaxed:
@@ -323,7 +323,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 60000, but must be within ``range(1,1<<32)``.
+        Defaults to 131000, but must be within ``range(1,1<<32)``.
 
     :type relaxed: bool
     :param relaxed:
--- a/MoinMoin/support/passlib/handlers/fshp.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/fshp.py	Mon Sep 05 23:44:04 2016 +0200
@@ -40,7 +40,7 @@
 
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 100000, must be between 1 and 4294967295, inclusive.
+        Defaults to 480000, must be between 1 and 4294967295, inclusive.
 
     :param variant:
         Optionally specifies variant of FSHP to use.
@@ -79,7 +79,7 @@
     #--HasRounds--
     # FIXME: should probably use different default rounds
     # based on the variant. setting for default variant (sha256) for now.
-    default_rounds = 100000 # current passlib default, FSHP uses 4096
+    default_rounds = 480000 # current passlib default, FSHP uses 4096
     min_rounds = 1 # set by FSHP
     max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
     rounds_cost = "linear"
--- a/MoinMoin/support/passlib/handlers/ldap_digests.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/ldap_digests.py	Mon Sep 05 23:44:04 2016 +0200
@@ -38,7 +38,7 @@
 # ldap helpers
 #=============================================================================
 class _Base64DigestHelper(uh.StaticHandler):
-    "helper for ldap_md5 / ldap_sha1"
+    """helper for ldap_md5 / ldap_sha1"""
     # XXX: could combine this with hex digests in digests.py
 
     ident = None # required - prefix identifier
@@ -48,7 +48,7 @@
 
     @classproperty
     def _hash_prefix(cls):
-        "tell StaticHandler to strip ident from checksum"
+        """tell StaticHandler to strip ident from checksum"""
         return cls.ident
 
     def _calc_checksum(self, secret):
@@ -58,7 +58,7 @@
         return b64encode(chk).decode("ascii")
 
 class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
-    "helper for ldap_salted_md5 / ldap_salted_sha1"
+    """helper for ldap_salted_md5 / ldap_salted_sha1"""
     setting_kwds = ("salt", "salt_size")
     checksum_chars = uh.PADDED_BASE64_CHARS
 
--- a/MoinMoin/support/passlib/handlers/md5_crypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/md5_crypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -191,7 +191,7 @@
 # handler
 #=============================================================================
 class _MD5_Common(uh.HasSalt, uh.GenericHandler):
-    "common code for md5_crypt and apr_md5_crypt"
+    """common code for md5_crypt and apr_md5_crypt"""
     #===================================================================
     # class attrs
     #===================================================================
--- a/MoinMoin/support/passlib/handlers/misc.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/misc.py	Mon Sep 05 23:44:04 2016 +0200
@@ -37,7 +37,7 @@
       all passwords will be allowed through if the hash is an empty string.
 
     .. deprecated:: 1.6
-        This has been deprecated due to it's "wildcard" feature,
+        This has been deprecated due to its "wildcard" feature,
         and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
     """
     name = "unix_fallback"
--- a/MoinMoin/support/passlib/handlers/mssql.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/mssql.py	Mon Sep 05 23:44:04 2016 +0200
@@ -64,7 +64,7 @@
 UIDENT = u("0x0100")
 
 def _ident_mssql(hash, csize, bsize):
-    "common identify for mssql 2000/2005"
+    """common identify for mssql 2000/2005"""
     if isinstance(hash, unicode):
         if len(hash) == csize and hash.startswith(UIDENT):
             return True
@@ -78,7 +78,7 @@
     return False
 
 def _parse_mssql(hash, csize, bsize, handler):
-    "common parser for mssql 2000/2005; returns 4 byte salt + checksum"
+    """common parser for mssql 2000/2005; returns 4 byte salt + checksum"""
     if isinstance(hash, unicode):
         if len(hash) == csize and hash.startswith(UIDENT):
             try:
--- a/MoinMoin/support/passlib/handlers/oracle.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/oracle.py	Mon Sep 05 23:44:04 2016 +0200
@@ -113,7 +113,7 @@
     :param salt:
         Optional salt string.
         If not specified, one will be autogenerated (this is recommended).
-        If specified, it must be 20 hexidecimal characters.
+        If specified, it must be 20 hexadecimal characters.
 
     :type relaxed: bool
     :param relaxed:
--- a/MoinMoin/support/passlib/handlers/pbkdf2.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/pbkdf2.py	Mon Sep 05 23:44:04 2016 +0200
@@ -28,7 +28,7 @@
 #
 #=============================================================================
 class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
-    "base class for various pbkdf2_{digest} algorithms"
+    """base class for various pbkdf2_{digest} algorithms"""
     #===================================================================
     # class attrs
     #===================================================================
@@ -84,7 +84,7 @@
         return pbkdf2(secret, self.salt, self.rounds, self.checksum_size, self._prf)
 
 def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
-    "create new Pbkdf2DigestHandler subclass for a specific hash"
+    """create new Pbkdf2DigestHandler subclass for a specific hash"""
     name = 'pbkdf2_' + hash_name
     if ident is None:
         ident = u("$pbkdf2-%s$") % (hash_name,)
@@ -135,9 +135,9 @@
 #------------------------------------------------------------------------
 # derived handlers
 #------------------------------------------------------------------------
-pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 60000, ident=u("$pbkdf2$"))
-pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 20000)
-pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 19000)
+pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$"))
+pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000)
+pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000)
 
 ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
 ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
--- a/MoinMoin/support/passlib/handlers/phpass.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/phpass.py	Mon Sep 05 23:44:04 2016 +0200
@@ -42,12 +42,12 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 17, must be between 7 and 30, inclusive.
+        Defaults to 19, must be between 7 and 30, inclusive.
         This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
 
     :type ident: str
     :param ident:
-        phpBB3 uses ``H`` instead of ``P`` for it's identifier,
+        phpBB3 uses ``H`` instead of ``P`` for its identifier,
         this may be set to ``H`` in order to generate phpBB3 compatible hashes.
         it defaults to ``P``.
 
@@ -75,7 +75,7 @@
     salt_chars = uh.HASH64_CHARS
 
     #--HasRounds--
-    default_rounds = 17
+    default_rounds = 19
     min_rounds = 7
     max_rounds = 30
     rounds_cost = "log2"
--- a/MoinMoin/support/passlib/handlers/scram.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/scram.py	Mon Sep 05 23:44:04 2016 +0200
@@ -15,7 +15,7 @@
 from passlib.utils import ab64_decode, ab64_encode, consteq, saslprep, \
                           to_native_str, xor_bytes, splitcomma
 from passlib.utils.compat import b, bytes, bascii_to_str, iteritems, \
-                                 PY3, u, unicode
+                                 PY3, u, unicode, native_string_types
 from passlib.utils.pbkdf2 import pbkdf2, get_prf, norm_hash_name
 import passlib.utils.handlers as uh
 # local
@@ -49,7 +49,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 20000, but must be within ``range(1,1<<32)``.
+        Defaults to 100000, but must be within ``range(1,1<<32)``.
 
     :type algs: list of strings
     :param algs:
@@ -102,7 +102,7 @@
     max_salt_size = 1024
 
     #--HasRounds--
-    default_rounds = 20000
+    default_rounds = 100000
     min_rounds = 1
     max_rounds = 2**32-1
     rounds_cost = "linear"
@@ -317,7 +317,7 @@
         return checksum
 
     def _norm_algs(self, algs):
-        "normalize algs parameter"
+        """normalize algs parameter"""
         # determine default algs value
         if algs is None:
             # derive algs list from checksum (if present).
@@ -332,7 +332,7 @@
             raise RuntimeError("checksum & algs kwds are mutually exclusive")
 
         # parse args value
-        if isinstance(algs, str):
+        if isinstance(algs, native_string_types):
             algs = splitcomma(algs)
         algs = sorted(norm_hash_name(alg, 'iana') for alg in algs)
         if any(len(alg)>9 for alg in algs):
@@ -348,7 +348,7 @@
 
     @classmethod
     def _bind_needs_update(cls, **settings):
-        "generate a deprecation detector for CryptContext to use"
+        """generate a deprecation detector for CryptContext to use"""
         # generate deprecation hook which marks hashes as deprecated
         # if they don't support a superset of current algs.
         algs = frozenset(cls(use_defaults=True, **settings).algs)
--- a/MoinMoin/support/passlib/handlers/sha1_crypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/sha1_crypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -47,7 +47,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 64000, must be between 1 and 4294967295, inclusive.
+        Defaults to 480000, must be between 1 and 4294967295, inclusive.
 
     :type relaxed: bool
     :param relaxed:
@@ -77,7 +77,7 @@
     salt_chars = uh.HASH64_CHARS
 
     #--HasRounds--
-    default_rounds = 64000 # current passlib default
+    default_rounds = 480000 # current passlib default
     min_rounds = 1 # really, this should be higher.
     max_rounds = 4294967295 # 32-bit integer limit
     rounds_cost = "linear"
--- a/MoinMoin/support/passlib/handlers/sha2_crypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/sha2_crypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -240,7 +240,7 @@
 
 class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
                    uh.GenericHandler):
-    "class containing common code shared by sha256_crypt & sha512_crypt"
+    """class containing common code shared by sha256_crypt & sha512_crypt"""
     #===================================================================
     # class attrs
     #===================================================================
@@ -374,7 +374,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 110000, must be between 1000 and 999999999, inclusive.
+        Defaults to 535000, must be between 1000 and 999999999, inclusive.
 
     :type implicit_rounds: bool
     :param implicit_rounds:
@@ -402,7 +402,7 @@
     ident = u("$5$")
     checksum_size = 43
     # NOTE: using 25/75 weighting of builtin & os_crypt backends
-    default_rounds = 110000
+    default_rounds = 535000
 
     #===================================================================
     # backends
@@ -435,7 +435,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 100000, must be between 1000 and 999999999, inclusive.
+        Defaults to 656000, must be between 1000 and 999999999, inclusive.
 
     :type implicit_rounds: bool
     :param implicit_rounds:
@@ -465,7 +465,7 @@
     checksum_size = 86
     _cdb_use_512 = True
     # NOTE: using 25/75 weighting of builtin & os_crypt backends
-    default_rounds = 100000
+    default_rounds = 656000
 
     #===================================================================
     # backend
--- a/MoinMoin/support/passlib/handlers/sun_md5_crypt.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/sun_md5_crypt.py	Mon Sep 05 23:44:04 2016 +0200
@@ -82,7 +82,7 @@
 del xr
 
 def raw_sun_md5_crypt(secret, rounds, salt):
-    "given secret & salt, return encoded sun-md5-crypt checksum"
+    """given secret & salt, return encoded sun-md5-crypt checksum"""
     global MAGIC_HAMLET
     assert isinstance(secret, bytes)
     assert isinstance(salt, bytes)
@@ -193,7 +193,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 5500, must be between 0 and 4294963199, inclusive.
+        Defaults to 34000, must be between 0 and 4294963199, inclusive.
 
     :type bare_salt: bool
     :param bare_salt:
@@ -231,7 +231,7 @@
     max_salt_size = None
     salt_chars = uh.HASH64_CHARS
 
-    default_rounds = 5500 # current passlib default
+    default_rounds = 34000 # current passlib default
     min_rounds = 0
     max_rounds = 4294963199 ##2**32-1-4096
         # XXX: ^ not sure what it does if past this bound... does 32 int roll over?
--- a/MoinMoin/support/passlib/handlers/windows.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/handlers/windows.py	Mon Sep 05 23:44:04 2016 +0200
@@ -40,7 +40,7 @@
         calculating digest. It defaults to ``cp437``, the most
         common encoding encountered.
 
-    Note that while this class outputs digests in lower-case hexidecimal,
+    Note that while this class outputs digests in lower-case hexadecimal,
     it will accept upper-case as well.
     """
     #===================================================================
@@ -116,7 +116,7 @@
 
     The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
 
-    Note that while this class outputs lower-case hexidecimal digests,
+    Note that while this class outputs lower-case hexadecimal digests,
     it will accept upper-case digests as well.
     """
     #===================================================================
@@ -228,7 +228,7 @@
         This keyword is case-insensitive, and should contain just the username
         (e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
 
-    Note that while this class outputs lower-case hexidecimal digests,
+    Note that while this class outputs lower-case hexadecimal digests,
     it will accept upper-case digests as well.
     """
     name = "msdcc"
--- a/MoinMoin/support/passlib/hosts.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/hosts.py	Mon Sep 05 23:44:04 2016 +0200
@@ -71,7 +71,7 @@
     # and can be introspected and used much more flexibly.
 
     def _iter_os_crypt_schemes():
-        "helper which iterates over supported os_crypt schemes"
+        """helper which iterates over supported os_crypt schemes"""
         found = False
         for name in unix_crypt_schemes:
             handler = get_crypt_handler(name)
--- a/MoinMoin/support/passlib/ifc.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/ifc.py	Mon Sep 05 23:44:04 2016 +0200
@@ -26,7 +26,7 @@
 #        return None
 
 def create_with_metaclass(meta):
-    "class decorator that re-creates class using metaclass"
+    """class decorator that re-creates class using metaclass"""
     # have to do things this way since abc not present in py25,
     # and py2/py3 have different ways of doing metaclasses.
     def builder(cls):
@@ -84,13 +84,13 @@
     @classmethod
     @abstractmethod
     def encrypt(cls, secret, **setting_and_context_kwds): # pragma: no cover -- abstract method
-        "encrypt secret, returning resulting hash"
+        """encrypt secret, returning resulting hash"""
         raise NotImplementedError("must be implemented by subclass")
 
     @classmethod
     @abstractmethod
     def verify(cls, secret, hash, **context_kwds): # pragma: no cover -- abstract method
-        "verify secret against hash, returns True/False"
+        """verify secret against hash, returns True/False"""
         raise NotImplementedError("must be implemented by subclass")
 
     #===================================================================
@@ -99,19 +99,19 @@
     @classmethod
     @abstractmethod
     def identify(cls, hash): # pragma: no cover -- abstract method
-        "check if hash belongs to this scheme, returns True/False"
+        """check if hash belongs to this scheme, returns True/False"""
         raise NotImplementedError("must be implemented by subclass")
 
     @classmethod
     @abstractmethod
     def genconfig(cls, **setting_kwds): # pragma: no cover -- abstract method
-        "compile settings into a configuration string for genhash()"
+        """compile settings into a configuration string for genhash()"""
         raise NotImplementedError("must be implemented by subclass")
 
     @classmethod
     @abstractmethod
     def genhash(cls, secret, config, **context_kwds): # pragma: no cover -- abstract method
-        "generated hash for secret, using settings from config/hash string"
+        """generated hash for secret, using settings from config/hash string"""
         raise NotImplementedError("must be implemented by subclass")
 
     #===================================================================
--- a/MoinMoin/support/passlib/registry.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/registry.py	Mon Sep 05 23:44:04 2016 +0200
@@ -9,6 +9,7 @@
 # pkg
 from passlib.exc import ExpectedTypeError, PasslibWarning
 from passlib.utils import is_crypt_handler
+from passlib.utils.compat import native_string_types
 # local
 __all__ = [
     "register_crypt_handler_path",
@@ -262,7 +263,8 @@
     name = handler.name
     _validate_handler_name(name)
     if _attr and _attr != name:
-        raise ValueError("handlers must be stored only under their own name")
+        raise ValueError("handlers must be stored only under their own name (%r != %r)" %
+                         (_attr, name))
 
     # check for existing handler
     other = _handlers.get(name)
@@ -310,7 +312,7 @@
         pass
 
     # normalize name (and if changed, check dict again)
-    assert isinstance(name, str), "name must be str instance"
+    assert isinstance(name, native_string_types), "name must be str instance"
     alt = name.replace("-","_").lower()
     if alt != name:
         warn("handler names should be lower-case, and use underscores instead "
@@ -338,7 +340,7 @@
         mod = __import__(modname, fromlist=[modattr], level=0)
 
         # first check if importing module triggered register_crypt_handler(),
-        # (this is discouraged due to it's magical implicitness)
+        # (this is discouraged due to its magical implicitness)
         handler = _handlers.get(name)
         if handler:
             # XXX: issue deprecation warning here?
@@ -394,7 +396,7 @@
         used only by the unittests.
 
     if loaded handler is found with specified name, it's removed.
-    if path to lazy load handler is found, its' removed.
+    if path to lazy load handler is found, it's removed.
 
     missing names are a noop.
 
--- a/MoinMoin/support/passlib/utils/__init__.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/__init__.py	Mon Sep 05 23:44:04 2016 +0200
@@ -130,7 +130,7 @@
 
     @property
     def __func__(self):
-        "py3 compatible alias"
+        """py3 compatible alias"""
         return self.im_func
 
 def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
@@ -221,7 +221,7 @@
 
     @property
     def __func__(self):
-        "py3 alias"
+        """py3 alias"""
         return self.im_func
 
 # works but not used
@@ -253,7 +253,7 @@
 
     The purpose of this function is to help prevent timing attacks
     during digest comparisons: the standard ``==`` operator aborts
-    after the first mismatched character, causing it's runtime to be
+    after the first mismatched character, causing its runtime to be
     proportional to the longest prefix shared by the two inputs.
     If an attacker is able to predict and control one of the two
     inputs, repeated queries can be leveraged to reveal information about
@@ -456,7 +456,7 @@
 # replace saslprep() with stub when stringprep is missing
 if stringprep is None: # pragma: no cover -- runtime detection
     def saslprep(source, param="value"):
-        "stub for saslprep()"
+        """stub for saslprep()"""
         raise NotImplementedError("saslprep() support requires the 'stringprep' "
                             "module, which is " + _stringprep_missing_reason)
 
@@ -503,11 +503,11 @@
 add_doc(int_to_bytes, "encode integer as single big-endian byte string")
 
 def xor_bytes(left, right):
-    "Perform bitwise-xor of two byte strings (must be same size)"
+    """Perform bitwise-xor of two byte strings (must be same size)"""
     return int_to_bytes(bytes_to_int(left) ^ bytes_to_int(right), len(left))
 
 def repeat_string(source, size):
-    "repeat or truncate <source> string, so it has length <size>"
+    """repeat or truncate <source> string, so it has length <size>"""
     cur = len(source)
     if size > cur:
         mult = (size+cur-1)//cur
@@ -519,7 +519,7 @@
 _UNULL = u("\x00")
 
 def right_pad_string(source, size, pad=None):
-    "right-pad or truncate <source> string, so it has length <size>"
+    """right-pad or truncate <source> string, so it has length <size>"""
     cur = len(source)
     if size > cur:
         if pad is None:
@@ -535,11 +535,11 @@
 _ASCII_TEST_UNICODE = _ASCII_TEST_BYTES.decode("ascii")
 
 def is_ascii_codec(codec):
-    "Test if codec is compatible with 7-bit ascii (e.g. latin-1, utf-8; but not utf-16)"
+    """Test if codec is compatible with 7-bit ascii (e.g. latin-1, utf-8; but not utf-16)"""
     return _ASCII_TEST_UNICODE.encode(codec) == _ASCII_TEST_BYTES
 
 def is_same_codec(left, right):
-    "Check if two codec names are aliases for same codec"
+    """Check if two codec names are aliases for same codec"""
     if left == right:
         return True
     if not (left and right):
@@ -549,7 +549,7 @@
 _B80 = b('\x80')[0]
 _U80 = u('\x80')
 def is_ascii_safe(source):
-    "Check if string (bytes or unicode) contains only 7-bit ascii"
+    """Check if string (bytes or unicode) contains only 7-bit ascii"""
     r = _B80 if isinstance(source, bytes) else _U80
     return all(c < r for c in source)
 
@@ -656,7 +656,7 @@
 
 @deprecated_function(deprecated="1.6", removed="1.7")
 def to_hash_str(source, encoding="ascii"): # pragma: no cover -- deprecated & unused
-    "deprecated, use to_native_str() instead"
+    """deprecated, use to_native_str() instead"""
     return to_native_str(source, encoding, param="hash")
 
 #=============================================================================
@@ -671,7 +671,7 @@
         A string of 64 unique characters,
         which will be used to encode successive 6-bit chunks of data.
         A character's position within the string should correspond
-        to it's 6-bit value.
+        to its 6-bit value.
 
     :param big:
         Whether the encoding should be big-endian (default False).
@@ -783,7 +783,7 @@
 
     @property
     def charmap(self):
-        "charmap as unicode"
+        """charmap as unicode"""
         return self.bytemap.decode("latin-1")
 
     #===================================================================
@@ -811,7 +811,7 @@
         return out
 
     def _encode_bytes_little(self, next_value, chunks, tail):
-        "helper used by encode_bytes() to handle little-endian encoding"
+        """helper used by encode_bytes() to handle little-endian encoding"""
         #
         # output bit layout:
         #
@@ -850,7 +850,7 @@
                 yield v2>>4
 
     def _encode_bytes_big(self, next_value, chunks, tail):
-        "helper used by encode_bytes() to handle big-endian encoding"
+        """helper used by encode_bytes() to handle big-endian encoding"""
         #
         # output bit layout:
         #
@@ -916,7 +916,7 @@
             raise ValueError("invalid character: %r" % (err.args[0],))
 
     def _decode_bytes_little(self, next_value, chunks, tail):
-        "helper used by decode_bytes() to handle little-endian encoding"
+        """helper used by decode_bytes() to handle little-endian encoding"""
         #
         # input bit layout:
         #
@@ -951,7 +951,7 @@
                 yield (v2>>2) | ((v3 & 0xF) << 4)
 
     def _decode_bytes_big(self, next_value, chunks, tail):
-        "helper used by decode_bytes() to handle big-endian encoding"
+        """helper used by decode_bytes() to handle big-endian encoding"""
         #
         # input bit layout:
         #
@@ -993,21 +993,21 @@
     # equivalent char with no padding bits set.
 
     def __make_padset(self, bits):
-        "helper to generate set of valid last chars & bytes"
+        """helper to generate set of valid last chars & bytes"""
         pset = set(c for i,c in enumerate(self.bytemap) if not i & bits)
         pset.update(c for i,c in enumerate(self.charmap) if not i & bits)
         return frozenset(pset)
 
     @memoized_property
     def _padinfo2(self):
-        "mask to clear padding bits, and valid last bytes (for strings 2 % 4)"
+        """mask to clear padding bits, and valid last bytes (for strings 2 % 4)"""
         # 4 bits of last char unused (lsb for big, msb for little)
         bits = 15 if self.big else (15<<2)
         return ~bits, self.__make_padset(bits)
 
     @memoized_property
     def _padinfo3(self):
-        "mask to clear padding bits, and valid last bytes (for strings 3 % 4)"
+        """mask to clear padding bits, and valid last bytes (for strings 3 % 4)"""
         # 2 bits of last char unused (lsb for big, msb for little)
         bits = 3 if self.big else (3<<4)
         return ~bits, self.__make_padset(bits)
@@ -1072,14 +1072,14 @@
     # transposed encoding/decoding
     #===================================================================
     def encode_transposed_bytes(self, source, offsets):
-        "encode byte string, first transposing source using offset list"
+        """encode byte string, first transposing source using offset list"""
         if not isinstance(source, bytes):
             raise TypeError("source must be bytes, not %s" % (type(source),))
         tmp = join_byte_elems(source[off] for off in offsets)
         return self.encode_bytes(tmp)
 
     def decode_transposed_bytes(self, source, offsets):
-        "decode byte string, then reverse transposition described by offset list"
+        """decode byte string, then reverse transposition described by offset list"""
         # NOTE: if transposition does not use all bytes of source,
         # the original can't be recovered... and join_byte_elems() will throw
         # an error because 1+ values in <buf> will be None.
@@ -1133,7 +1133,7 @@
     #---------------------------------------------------------------
 
     def decode_int6(self, source):
-        "decode single character -> 6 bit integer"
+        """decode single character -> 6 bit integer"""
         if not isinstance(source, bytes):
             raise TypeError("source must be bytes, not %s" % (type(source),))
         if len(source) != 1:
@@ -1147,7 +1147,7 @@
             raise ValueError("invalid character")
 
     def decode_int12(self, source):
-        "decodes 2 char string -> 12-bit integer"
+        """decodes 2 char string -> 12-bit integer"""
         if not isinstance(source, bytes):
             raise TypeError("source must be bytes, not %s" % (type(source),))
         if len(source) != 2:
@@ -1162,7 +1162,7 @@
             raise ValueError("invalid character")
 
     def decode_int24(self, source):
-        "decodes 4 char string -> 24-bit integer"
+        """decodes 4 char string -> 24-bit integer"""
         if not isinstance(source, bytes):
             raise TypeError("source must be bytes, not %s" % (type(source),))
         if len(source) != 4:
@@ -1216,7 +1216,7 @@
     #---------------------------------------------------------------
 
     def encode_int6(self, value):
-        "encodes 6-bit integer -> single hash64 character"
+        """encodes 6-bit integer -> single hash64 character"""
         if value < 0 or value > 63:
             raise ValueError("value out of range")
         if PY3:
@@ -1225,7 +1225,7 @@
             return self._encode64(value)
 
     def encode_int12(self, value):
-        "encodes 12-bit integer -> 2 char string"
+        """encodes 12-bit integer -> 2 char string"""
         if value < 0 or value > 0xFFF:
             raise ValueError("value out of range")
         raw = [value & 0x3f, (value>>6) & 0x3f]
@@ -1234,7 +1234,7 @@
         return join_byte_elems(imap(self._encode64, raw))
 
     def encode_int24(self, value):
-        "encodes 24-bit integer -> 4 char string"
+        """encodes 24-bit integer -> 4 char string"""
         if value < 0 or value > 0xFFFFFF:
             raise ValueError("value out of range")
         raw = [value & 0x3f, (value>>6) & 0x3f,
@@ -1258,7 +1258,7 @@
     #===================================================================
 
 class LazyBase64Engine(Base64Engine):
-    "Base64Engine which delays initialization until it's accessed"
+    """Base64Engine which delays initialization until it's accessed"""
     _lazy_opts = None
 
     def __init__(self, *args, **kwds):
@@ -1331,6 +1331,7 @@
 try:
     from crypt import crypt as _crypt
 except ImportError: # pragma: no cover
+    _crypt = None
     has_crypt = False
     def safe_crypt(secret, hash):
         return None
@@ -1451,13 +1452,13 @@
     has_urandom = False
 
 def genseed(value=None):
-    "generate prng seed value from system resources"
+    """generate prng seed value from system resources"""
     from hashlib import sha512
     text = u("%s %s %s %s %.15f %.15f %s") % (
         # if caller specified a seed value, mix it in
         value,
 
-        # if caller's seed value was an RNG, mix in bits from it's state
+        # if caller's seed value was an RNG, mix in bits from its state
         value.getrandbits(1<<15) if hasattr(value, "getrandbits") else None,
 
         # add current process id
@@ -1572,7 +1573,7 @@
         )
 
 def is_crypt_handler(obj):
-    "check if object follows the :ref:`password-hash-api`"
+    """check if object follows the :ref:`password-hash-api`"""
     # XXX: change to use isinstance(obj, PasswordHash) under py26+?
     return all(hasattr(obj, name) for name in _handler_attrs)
 
@@ -1583,7 +1584,7 @@
         )
 
 def is_crypt_context(obj):
-    "check if object appears to be a :class:`~passlib.context.CryptContext` instance"
+    """check if object appears to be a :class:`~passlib.context.CryptContext` instance"""
     # XXX: change to use isinstance(obj, CryptContext)?
     return all(hasattr(obj, name) for name in _context_attrs)
 
@@ -1593,12 +1594,12 @@
 ##    return hasattr(handler, "set_backend")
 
 def has_rounds_info(handler):
-    "check if handler provides the optional :ref:`rounds information <rounds-attributes>` attributes"
+    """check if handler provides the optional :ref:`rounds information <rounds-attributes>` attributes"""
     return ('rounds' in handler.setting_kwds and
             getattr(handler, "min_rounds", None) is not None)
 
 def has_salt_info(handler):
-    "check if handler provides the optional :ref:`salt information <salt-attributes>` attributes"
+    """check if handler provides the optional :ref:`salt information <salt-attributes>` attributes"""
     return ('salt' in handler.setting_kwds and
             getattr(handler, "min_salt_size", None) is not None)
 
--- a/MoinMoin/support/passlib/utils/_blowfish/__init__.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/_blowfish/__init__.py	Mon Sep 05 23:44:04 2016 +0200
@@ -18,11 +18,11 @@
 
 Status
 ------
-This implementation is usuable, but is an order of magnitude too slow to be
-usuable with real security. For "ok" security, BCrypt hashes should have at
+This implementation is usable, but is an order of magnitude too slow to be
+usable with real security. For "ok" security, BCrypt hashes should have at
 least 2**11 rounds (as of 2011). Assuming a desired response time <= 100ms,
 this means a BCrypt implementation should get at least 20 rounds/ms in order
-to be both usuable *and* secure. On a 2 ghz cpu, this implementation gets
+to be both usable *and* secure. On a 2 ghz cpu, this implementation gets
 roughly 0.09 rounds/ms under CPython (220x too slow), and 1.9 rounds/ms
 under PyPy (10x too slow).
 
@@ -55,7 +55,7 @@
 import struct
 # pkg
 from passlib.utils import bcrypt64, getrandbytes, rng
-from passlib.utils.compat import b, bytes, BytesIO, unicode, u
+from passlib.utils.compat import b, bytes, BytesIO, unicode, u, native_string_types
 from passlib.utils._blowfish.unrolled import BlowfishEngine
 # local
 __all__ = [
@@ -98,19 +98,15 @@
     #===================================================================
 
     # parse ident
-    assert isinstance(ident, unicode)
-    if ident == u('2'):
-        minor = 0
-    elif ident == u('2a'):
-        minor = 1
-        # XXX: how to indicate caller wants to use crypt_blowfish's
-        # workaround variant of 2a?
+    assert isinstance(ident, native_string_types)
+    add_null_padding = True
+    if ident == u('2a') or ident == u('2y') or ident == u('2b'):
+        pass
+    elif ident == u('2'):
+        add_null_padding = False
     elif ident == u('2x'):
         raise ValueError("crypt_blowfish's buggy '2x' hashes are not "
                          "currently supported")
-    elif ident == u('2y'):
-        # crypt_blowfish compatibility ident which guarantees compat w/ 2a
-        minor = 1
     else:
         raise ValueError("unknown ident: %r" % (ident,))
 
@@ -124,7 +120,7 @@
 
     # prepare password
     assert isinstance(password, bytes)
-    if minor > 0:
+    if add_null_padding:
         password += BNULL
 
     # validate rounds
--- a/MoinMoin/support/passlib/utils/_blowfish/_gen_files.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/_blowfish/_gen_files.py	Mon Sep 05 23:44:04 2016 +0200
@@ -17,7 +17,7 @@
 
 
 def indent_block(block, padding):
-    "ident block of text"
+    """ident block of text"""
     lines = block.split("\n")
     return "\n".join(
         padding + line if line else ""
--- a/MoinMoin/support/passlib/utils/_blowfish/base.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/_blowfish/base.py	Mon Sep 05 23:44:04 2016 +0200
@@ -339,7 +339,7 @@
     # blowfish routines
     #===================================================================
     def encipher(self, l, r):
-        "loop version of blowfish encipher routine"
+        """loop version of blowfish encipher routine"""
         P, S = self.P, self.S
         l ^= P[0]
         i = 1
@@ -355,7 +355,7 @@
     # NOTE: decipher is same as above, just with reversed(P) instead.
 
     def expand(self, key_words):
-        "perform stock Blowfish keyschedule setup"
+        """perform stock Blowfish keyschedule setup"""
         assert len(key_words) >= 18, "key_words must be at least as large as P"
         P, S, encipher = self.P, self.S, self.encipher
 
@@ -379,7 +379,7 @@
     # eks-blowfish routines
     #===================================================================
     def eks_salted_expand(self, key_words, salt_words):
-        "perform EKS' salted version of Blowfish keyschedule setup"
+        """perform EKS' salted version of Blowfish keyschedule setup"""
         # NOTE: this is the same as expand(), except for the addition
         #       of the operations involving *salt_words*.
 
@@ -416,7 +416,7 @@
                 i += 2
 
     def eks_repeated_expand(self, key_words, salt_words, rounds):
-        "perform rounds stage of EKS keyschedule setup"
+        """perform rounds stage of EKS keyschedule setup"""
         expand = self.expand
         n = 0
         while n < rounds:
@@ -425,7 +425,7 @@
             n += 1
 
     def repeat_encipher(self, l, r, count):
-        "repeatedly apply encipher operation to a block"
+        """repeatedly apply encipher operation to a block"""
         encipher = self.encipher
         n = 0
         while n < count:
--- a/MoinMoin/support/passlib/utils/compat.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/compat.py	Mon Sep 05 23:44:04 2016 +0200
@@ -97,6 +97,7 @@
         return s.encode("latin-1")
 
     base_string_types = (unicode, bytes)
+    native_string_types = (unicode,)
 
 else:
     unicode = builtins.unicode
@@ -111,6 +112,7 @@
         return s
 
     base_string_types = basestring
+    native_string_types = (basestring,)
 
 #=============================================================================
 # unicode & bytes helpers
@@ -253,7 +255,7 @@
 if PY_MAX_25:
     _undef = object()
     def next(itr, default=_undef):
-        "compat wrapper for next()"
+        """compat wrapper for next()"""
         if default is _undef:
             return itr.next()
         try:
@@ -282,7 +284,7 @@
 # introspection
 #=============================================================================
 def exc_err():
-    "return current error object (to avoid try/except syntax change)"
+    """return current error object (to avoid try/except syntax change)"""
     return sys.exc_info()[1]
 
 if PY3:
@@ -291,7 +293,7 @@
     method_function_attr = "im_func"
 
 def get_method_function(func):
-    "given (potential) method, return underlying function"
+    """given (potential) method, return underlying function"""
     return getattr(func, method_function_attr, func)
 
 #=============================================================================
@@ -366,7 +368,7 @@
 from types import ModuleType
 
 def _import_object(source):
-    "helper to import object from module; accept format `path.to.object`"
+    """helper to import object from module; accept format `path.to.object`"""
     modname, modattr = source.rsplit(".",1)
     mod = __import__(modname, fromlist=[modattr], level=0)
     return getattr(mod, modattr)
--- a/MoinMoin/support/passlib/utils/des.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/des.py	Mon Sep 05 23:44:04 2016 +0200
@@ -81,7 +81,7 @@
 PCXROT = IE3264 = SPE = CF6464 = None
 
 def _load_tables():
-    "delay loading tables until they are actually needed"
+    """delay loading tables until they are actually needed"""
     global PCXROT, IE3264, SPE, CF6464
 
     #---------------------------------------------------------------
@@ -612,7 +612,7 @@
 _EXPAND_ITER = irange(49,-7,-7)
 
 def expand_des_key(key):
-    "convert DES from 7 bytes to 8 bytes (by inserting empty parity bits)"
+    """convert DES from 7 bytes to 8 bytes (by inserting empty parity bits)"""
     if isinstance(key, bytes):
         if len(key) != 7:
             raise ValueError("key must be 7 bytes in size")
@@ -631,7 +631,7 @@
     return join_byte_values(((key>>shift) & 0x7f)<<1 for shift in _EXPAND_ITER)
 
 def shrink_des_key(key):
-    "convert DES key from 8 bytes to 7 bytes (by discarding the parity bits)"
+    """convert DES key from 8 bytes to 7 bytes (by discarding the parity bits)"""
     if isinstance(key, bytes):
         if len(key) != 8:
             raise ValueError("key must be 8 bytes in size")
@@ -666,7 +666,7 @@
 
     :arg salt:
         Optional 24-bit integer used to mutate the base DES algorithm in a
-        manner specific to :class:`~passlib.hash.des_crypt` and it's variants.
+        manner specific to :class:`~passlib.hash.des_crypt` and its variants.
         The default value ``0`` provides the normal (unsalted) DES behavior.
         The salt functions as follows:
         if the ``i``'th bit of ``salt`` is set,
@@ -675,7 +675,7 @@
     :arg rounds:
         Optional number of rounds of to apply the DES key schedule.
         the default (``rounds=1``) provides the normal DES behavior,
-        but :class:`~passlib.hash.des_crypt` and it's variants use
+        but :class:`~passlib.hash.des_crypt` and its variants use
         alternate rounds values.
 
     :raises TypeError: if any of the provided args are of the wrong type.
@@ -779,7 +779,7 @@
     # NOTE: generation was modified to output two elements at a time,
     # so that per-round loop could do two passes at once.
     def _iter_key_schedule(ks_odd):
-        "given 64-bit key, iterates over the 8 (even,odd) key schedule pairs"
+        """given 64-bit key, iterates over the 8 (even,odd) key schedule pairs"""
         for p_even, p_odd in PCXROT:
             ks_even = _permute(ks_odd, p_even)
             ks_odd = _permute(ks_even, p_odd)
--- a/MoinMoin/support/passlib/utils/handlers.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/handlers.py	Mon Sep 05 23:44:04 2016 +0200
@@ -88,14 +88,14 @@
 _UZERO = u("0")
 
 def validate_secret(secret):
-    "ensure secret has correct type & size"
+    """ensure secret has correct type & size"""
     if not isinstance(secret, base_string_types):
         raise exc.ExpectedStringError(secret, "secret")
     if len(secret) > MAX_PASSWORD_SIZE:
         raise exc.PasswordSizeError()
 
 def to_unicode_for_identify(hash):
-    "convert hash to unicode for identify method"
+    """convert hash to unicode for identify method"""
     if isinstance(hash, unicode):
         return hash
     elif isinstance(hash, bytes):
@@ -584,7 +584,7 @@
 
     @staticmethod
     def _sanitize(value, char=u("*")):
-        "default method to obscure sensitive fields"
+        """default method to obscure sensitive fields"""
         if value is None:
             return None
         if isinstance(value, bytes):
@@ -606,7 +606,7 @@
         (with the extra keyword *checksum*).
 
         this method may not work correctly for all hashes,
-        and may not be available on some few. it's interface may
+        and may not be available on some few. its interface may
         change in future releases, if it's kept around at all.
 
         :arg hash: hash to parse
@@ -634,7 +634,7 @@
 
     @classmethod
     def bitsize(cls, **kwds):
-        "[experimental method] return info about bitsizes of hash"
+        """[experimental method] return info about bitsizes of hash"""
         try:
             info = super(GenericHandler, cls).bitsize(**kwds)
         except AttributeError:
@@ -692,7 +692,7 @@
 
     @classmethod
     def _norm_hash(cls, hash):
-        "helper for subclasses to normalize case if needed"
+        """helper for subclasses to normalize case if needed"""
         return hash
 
     def to_string(self):
@@ -737,7 +737,7 @@
         hash = wrapper_cls.genhash(secret, None, **context)
         warn("%r should be updated to implement StaticHandler._calc_checksum() "
              "instead of StaticHandler.genhash(), support for the latter "
-             "style will be removed in Passlib 1.8" % (cls),
+             "style will be removed in Passlib 1.8" % cls,
              DeprecationWarning)
         return str_to_uascii(hash)
 
@@ -920,7 +920,7 @@
 
     Class Attributes
     ================
-    In order for :meth:`!_norm_salt` to do it's job, the following
+    In order for :meth:`!_norm_salt` to do its job, the following
     attributes should be provided by the handler subclass:
 
     .. attribute:: min_salt_size
@@ -986,12 +986,12 @@
 
     @classproperty
     def default_salt_size(cls):
-        "default salt size (defaults to *max_salt_size*)"
+        """default salt size (defaults to *max_salt_size*)"""
         return cls.max_salt_size
 
     @classproperty
     def default_salt_chars(cls):
-        "charset used to generate new salt strings (defaults to *salt_chars*)"
+        """charset used to generate new salt strings (defaults to *salt_chars*)"""
         return cls.salt_chars
 
     # private helpers for HasRawSalt, shouldn't be used by subclasses
@@ -1082,7 +1082,7 @@
     @staticmethod
     def _truncate_salt(salt, mx):
         # NOTE: some hashes (e.g. bcrypt) has structure within their
-        # salt string. this provides a method to overide to perform
+        # salt string. this provides a method to override to perform
         # the truncation properly
         return salt[:mx]
 
@@ -1095,7 +1095,7 @@
 
     @classmethod
     def bitsize(cls, salt_size=None, **kwds):
-        "[experimental method] return info about bitsizes of hash"
+        """[experimental method] return info about bitsizes of hash"""
         info = super(HasSalt, cls).bitsize(**kwds)
         if salt_size is None:
             salt_size = cls.default_salt_size
@@ -1143,7 +1143,7 @@
 
     Class Attributes
     ================
-    In order for :meth:`!_norm_rounds` to do it's job, the following
+    In order for :meth:`!_norm_rounds` to do its job, the following
     attributes must be provided by the handler subclass:
 
     .. attribute:: min_rounds
@@ -1259,7 +1259,7 @@
 
     @classmethod
     def bitsize(cls, rounds=None, vary_rounds=.1, **kwds):
-        "[experimental method] return info about bitsizes of hash"
+        """[experimental method] return info about bitsizes of hash"""
         info = super(HasRounds, cls).bitsize(**kwds)
         # NOTE: this essentially estimates how many bits of "salt"
         # can be added by varying the rounds value just a little bit.
@@ -1448,7 +1448,10 @@
         return name
 
     def _calc_checksum_backend(self, secret):
-        "stub for _calc_checksum_backend(), default backend will be selected first time stub is called"
+        """
+        stub for _calc_checksum_backend(),
+        the default backend will be selected the first time stub is called.
+        """
         # if we got here, no backend has been loaded; so load default backend
         assert not self._backend, "set_backend() failed to replace lazy loader"
         self.set_backend()
@@ -1458,7 +1461,7 @@
         return self._calc_checksum_backend(secret)
 
     def _calc_checksum(self, secret):
-        "wrapper for backend, for common code"""
+        """wrapper for backend, for common code"""
         return self._calc_checksum_backend(secret)
 
 #=============================================================================
@@ -1605,13 +1608,13 @@
         return list(attrs)
 
     def __getattr__(self, attr):
-        "proxy most attributes from wrapped class (e.g. rounds, salt size, etc)"
+        """proxy most attributes from wrapped class (e.g. rounds, salt size, etc)"""
         if attr in self._proxy_attrs:
             return getattr(self.wrapped, attr)
         raise AttributeError("missing attribute: %r" % (attr,))
 
     def _unwrap_hash(self, hash):
-        "given hash belonging to wrapper, return orig version"
+        """given hash belonging to wrapper, return orig version"""
         # NOTE: assumes hash has been validated as unicode already
         prefix = self.prefix
         if not hash.startswith(prefix):
@@ -1620,7 +1623,7 @@
         return self.orig_prefix + hash[len(prefix):]
 
     def _wrap_hash(self, hash):
-        "given orig hash; return one belonging to wrapper"
+        """given orig hash; return one belonging to wrapper"""
         # NOTE: should usually be native string.
         # (which does mean extra work under py2, but not py3)
         if isinstance(hash, bytes):
--- a/MoinMoin/support/passlib/utils/md4.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/md4.py	Mon Sep 05 23:44:04 2016 +0200
@@ -56,7 +56,7 @@
 
     .. method:: hexdigest
 
-        return hexdecimal version of digest
+        return hexadecimal version of digest
     """
     # FIXME: make this follow hash object PEP better.
     # FIXME: this isn't threadsafe
@@ -146,7 +146,7 @@
     ]
 
     def _process(self, block):
-        "process 64 byte block"
+        """process 64 byte block"""
         # unpack block into 16 32-bit ints
         X = struct.unpack("<16I", block)
 
@@ -258,7 +258,7 @@
 if _has_native_md4():
     # overwrite md4 class w/ hashlib wrapper
     def md4(content=None):
-        "wrapper for hashlib.new('md4')"
+        """wrapper for hashlib.new('md4')"""
         return hashlib.new('md4', content or b(''))
 
 #=============================================================================
--- a/MoinMoin/support/passlib/utils/pbkdf2.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/utils/pbkdf2.py	Mon Sep 05 23:44:04 2016 +0200
@@ -142,7 +142,7 @@
 _trans_36 = join_byte_values((x ^ 0x36) for x in irange(256))
 
 def _get_hmac_prf(digest):
-    "helper to return HMAC prf for specific digest"
+    """helper to return HMAC prf for specific digest"""
     def tag_wrapper(prf):
         prf.__name__ = "hmac_" + digest
         prf.__doc__ = ("hmac_%s(key, msg) -> digest;"
@@ -150,7 +150,7 @@
                        digest)
 
     if _EVP and digest == "sha1":
-        # use m2crypto function directly for sha1, since that's it's default digest
+        # use m2crypto function directly for sha1, since that's its default digest
         try:
             result = _EVP.hmac(b('x'),b('y'))
         except ValueError: # pragma: no cover
@@ -199,7 +199,7 @@
 _prf_cache = {}
 
 def _clear_prf_cache():
-    "helper for unit tests"
+    """helper for unit tests"""
     _prf_cache.clear()
 
 def get_prf(name):
--- a/MoinMoin/support/passlib/win32.py	Mon Sep 05 23:29:03 2016 +0200
+++ b/MoinMoin/support/passlib/win32.py	Mon Sep 05 23:44:04 2016 +0200
@@ -51,9 +51,9 @@
 raw_nthash = nthash.raw_nthash
 
 def raw_lmhash(secret, encoding="ascii", hex=False):
-    "encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex"
+    """encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex"""
     # NOTE: various references say LMHASH uses the OEM codepage of the host
-    #       for it's encoding. until a clear reference is found,
+    #       for its encoding. until a clear reference is found,
     #       as well as a path for getting the encoding,
     #       letting this default to "ascii" to prevent incorrect hashes
     #       from being made w/o user explicitly choosing an encoding.