flufl.password-1.3/0000775000175000017500000000000012410633530014605 5ustar barrybarry00000000000000flufl.password-1.3/PKG-INFO0000664000175000017500000000665212410633530015713 0ustar barrybarry00000000000000Metadata-Version: 1.1 Name: flufl.password Version: 1.3 Summary: Password hashing and verification. Home-page: http://launchpad.net/flufl.password Author: Barry Warsaw Author-email: barry@python.org License: LGPLv3 Download-URL: https://launchpad.net/flufl.password/+download Description: ============== flufl.password ============== Password hashing and verification. The ``flufl.password`` library provides hashing and verification of passwords. License ======= This file is part of flufl.password. flufl.password is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 of the License. flufl.password is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with flufl.password. If not, see . ======================= NEWS for flufl.password ======================= 1.3 (2014-09-24) ================ * Fix documentation bug. (LP: #1026403) * Purge all references to `distribute`. * Describe the switch to git and the repository move. 1.2.1 (2012-04-19) ================== * Add classifiers to setup.py and make the long description more compatible with the Cheeseshop. * Other changes to make the Cheeseshop page look nicer. (LP: #680136) * setup_helper.py version 2.1. 1.2 (2012-01-23) ================ * Fix some packaging issues. * Remove tox.ini. * Bump Copyright years. * Update standard template. * Eliminate the need to use 2to3, and fix some Python 3 deprecations. 1.1.1 (2012-01-01) ================== * Ensure all built-in schemes are registered by importing them in the __init__.py file. 1.1 (2011-12-31) ================ * Add user-friendly password generation API. 1.0 (2011-12-31) ================ * Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules flufl.password-1.3/setup.py0000664000175000017500000000421312410627537016331 0ustar barrybarry00000000000000# Copyright (C) 2004-2014 by Barry A. Warsaw # # This file is part of flufl.password. # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . from setup_helpers import ( description, get_version, long_description, require_python) from setuptools import setup, find_packages require_python(0x20600f0) __version__ = get_version('flufl/password/__init__.py') setup( name='flufl.password', version=__version__, namespace_packages=['flufl'], packages=find_packages(), include_package_data=True, maintainer='Barry Warsaw', maintainer_email='barry@python.org', description=description('README.rst'), long_description=long_description('README.rst', 'flufl/password/NEWS.rst'), license='LGPLv3', url='http://launchpad.net/flufl.password', download_url='https://launchpad.net/flufl.password/+download', test_suite='flufl.password.tests', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: ' 'GNU Lesser General Public License v3 or later (LGPLv3+)', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) flufl.password-1.3/flufl/0000775000175000017500000000000012410633530015715 5ustar barrybarry00000000000000flufl.password-1.3/flufl/password/0000775000175000017500000000000012410633530017557 5ustar barrybarry00000000000000flufl.password-1.3/flufl/password/schemes.py0000664000175000017500000001564412410627540021576 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Password hashing and verification schemes. Represents passwords using RFC 2307 syntax (as best we can tell). """ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'ClearTextPasswordScheme', 'NoPasswordScheme', 'PBKDF2PasswordScheme', 'PasswordScheme', 'SHAPasswordScheme', 'SSHAPasswordScheme', ] import os import hmac import hashlib from array import array from base64 import urlsafe_b64decode as decode from base64 import urlsafe_b64encode as encode from functools import partial from flufl.password._registry import register from flufl.password._utils import BadPasswordFormatError SALT_LENGTH = 20 # bytes ITERATIONS = 2000 class PasswordScheme: """Password scheme base class.""" TAG = '' @staticmethod def make_secret(password): """Return the hashed password. :param password: The clear text password. :type password: bytes or utf-8 encoded string. :return: The encrypted password. :rtype: bytes """ raise NotImplementedError @classmethod def check_response(cls, hashed, cleartext, scheme=None): """Check a hashed password against a clear text response. :param hashed: The user's stored hashed password, without the scheme prefix (i.e. strip off the *{SCHEME}* part first). :type hashed: bytes :param cleartext: The clear text password entered by the user for authentication. :type cleartext: bytes or utf-8 encoded string. :param scheme: The scheme tag i.e. the *{SCHEME}* part without the braces. This can be omitted when called on the class directly, unless the scheme puts some additional information in there. It is always provided by the `verify()` API. :type response: bytes or utf-8 encoded string. :return: True if the clear text matches the hash :rtype: bool """ return hashed == cls.make_secret(cleartext) @register class NoPasswordScheme(PasswordScheme): """A password scheme without passwords.""" TAG = 'NONE' @staticmethod def make_secret(password): """See `PasswordScheme`.""" return b'' @classmethod def check_response(cls, hashed, cleartext, scheme=None): """See `PasswordScheme`.""" return False @register class ClearTextPasswordScheme(PasswordScheme): """A password scheme that stores clear text passwords.""" TAG = 'CLEARTEXT' @staticmethod def make_secret(password): """See `PasswordScheme`.""" return password @register class SHAPasswordScheme(PasswordScheme): """A password scheme that encodes the password using SHA1.""" TAG = 'SHA' @staticmethod def make_secret(password): """See `PasswordScheme`.""" h = hashlib.sha1(password) return encode(h.digest()) @register class SSHAPasswordScheme(PasswordScheme): """A password scheme that encodes the password using salted SHA1.""" TAG = 'SSHA' @staticmethod def _make_salted_hash(password, salt): h = hashlib.sha1(password) h.update(salt) return encode(h.digest() + salt) @staticmethod def make_secret(password): """See `PasswordScheme`.""" salt = os.urandom(SALT_LENGTH) return SSHAPasswordScheme._make_salted_hash(password, salt) @classmethod def check_response(cls, hashed, cleartext, scheme=None): """See `PasswordScheme`.""" # The salt is always 20 bytes and always tacked onto the end. decoded = decode(hashed) salt = decoded[20:] return hashed == cls._make_salted_hash(cleartext, salt) def _bytes_of(array_obj): # Avoid DeprecationWarnings in Python 3. try: return array_obj.tobytes() except AttributeError: return array_obj.tostring() # Basic algorithm given by Bob Fleck @register class PBKDF2PasswordScheme(PasswordScheme): """RFC 2989 password encoding scheme.""" # This is a bit nasty if we wanted a different prf or iterations. OTOH, # we really have no clue what the standard LDAP-ish specification for # those options is. TAG = 'PBKDF2 SHA {0}'.format(ITERATIONS) @staticmethod def _pbkdf2(password, salt, iterations): """From RFC2898 sec. 5.2. Simplified to handle only 20 byte output case. Output of 20 bytes means always exactly one block to handle, and a constant block counter appended to the salt in the initial hmac update. """ # We do it this way because the array() constructor takes a 'native # string'. You can't use unicodes in Python 2 or bytes in Python 3. array_of_signedints = partial(array, str('i')) h = hmac.new(password, None, hashlib.sha1) prf = h.copy() prf.update(salt + b'\x00\x00\x00\x01') T = U = array_of_signedints(prf.digest()) while iterations: prf = h.copy() prf.update(_bytes_of(U)) U = array_of_signedints(prf.digest()) T = array_of_signedints((t ^ u for t, u in zip(T, U))) iterations -= 1 return _bytes_of(T) @staticmethod def make_secret(password): """See `PasswordScheme`. From RFC2898 sec. 5.2. Simplified to handle only 20 byte output case. Output of 20 bytes means always exactly one block to handle, and a constant block counter appended to the salt in the initial hmac update. """ salt = os.urandom(SALT_LENGTH) digest = PBKDF2PasswordScheme._pbkdf2(password, salt, ITERATIONS) return encode(digest + salt) @classmethod def check_response(cls, hashed, cleartext, scheme): """See `PasswordScheme`.""" parts = scheme.split() if (len(parts) != 3 or parts[0].upper() != 'PBKDF2' or parts[1].upper() != 'SHA' ): raise BadPasswordFormatError(scheme) try: iterations = int(parts[2]) except ValueError: raise BadPasswordFormatError(scheme) decoded = decode(hashed) salt = decoded[20:] secret = decoded[:20] return secret == cls._pbkdf2(cleartext, salt, iterations) flufl.password-1.3/flufl/password/_generate.py0000664000175000017500000000357512410627540022100 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """User-friendly password generation.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'generate', ] import random from itertools import chain, product from string import ascii_lowercase EMPTYSTRING = '' _vowels = tuple('aeiou') _consonants = tuple(c for c in ascii_lowercase if c not in _vowels) _syllables = tuple(x + y for (x, y) in chain(product(_vowels, _consonants), product(_consonants, _vowels))) def generate(length=8): """Make a random *user friendly* password. Such passwords are nominally easier to pronounce and thus remember. Their security in relationship to purely random passwords has not been determined. :param length: Minimum length in characters for the resulting password. The password will always be an even number of characters. The default is to create passwords of length 8. :type length: int :return: The user friendly password. :rtype: string """ syllables = [] while len(syllables) * 2 < length: syllables.append(random.choice(_syllables)) return EMPTYSTRING.join(syllables)[:length] flufl.password-1.3/flufl/password/_registry.py0000664000175000017500000000402712410627540022147 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Scheme registry.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'BadPasswordSchemeError', 'lookup', 'register', ] _registry = {} class BadPasswordSchemeError(Exception): """An unknown password scheme tag was given.""" def register(scheme_class): """Register a scheme by its tag. This is intended to be used as a class descriptor. :param scheme_class: A password scheme. :type scheme_class: A class which has a `TAG` attribute. The tag may contain space-separated words with additional information, but the scheme will be registered using the first word as the key. :return: scheme_class """ key = scheme_class.TAG.split()[0] _registry[key] = scheme_class return scheme_class def lookup(tag): """Return a scheme class by its tag. :param tag: The tag to search for. The tag may contain space-separated words with additional information, but the scheme will be looked up using the first word as the key. :type tag: string :return: The matching scheme class. :rtype: class :raises BadPasswordSchemeError: when the named scheme can't be found. """ key = tag.split()[0] try: return _registry[key] except KeyError: raise BadPasswordSchemeError(tag) flufl.password-1.3/flufl/password/__init__.py0000664000175000017500000000254212410632701021672 0ustar barrybarry00000000000000# Copyright (C) 2004-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Package init.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'BadPasswordFormatError', 'BadPasswordSchemeError', '__version__', 'generate', 'lookup', 'make_secret', 'register', 'verify', ] __version__ = '1.3' from ._hash import make_secret from ._generate import generate from ._registry import BadPasswordSchemeError, lookup, register from ._utils import BadPasswordFormatError from ._verify import verify # Register the built-in schemes by import, but don't expose this in the API. # Users should import specific schemes explicitly. from . import schemes as _schemes flufl.password-1.3/flufl/password/_utils.py0000664000175000017500000000236712410627540021444 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Various utilities.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'BadPasswordFormatError', 'split', ] import re SCHEME_RE = r'{(?P[^}]+?)}(?P.*)'.encode() class BadPasswordFormatError(Exception): """A badly formatted password hash was given.""" def split(hashed): mo = re.match(SCHEME_RE, hashed, re.IGNORECASE) if not mo: raise BadPasswordFormatError scheme, secret = mo.groups(('scheme', 'rest')) return scheme.decode('utf-8'), secret flufl.password-1.3/flufl/password/conf.py0000664000175000017500000001536512410627565021103 0ustar barrybarry00000000000000# -*- coding: utf-8 -*- # # flufl.password documentation build configuration file, created by # sphinx-quickstart on Thu Jan 7 18:41:30 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['../../_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'README' # General information about the project. project = 'flufl.password' copyright = '2004-2014, Barry A. Warsaw' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # from flufl.password import __version__ # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build', 'build', 'flufl.password.egg-info', 'distribute-0.6.10'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['../../_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'fluflpassworddoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('README.txt', 'fluflpassword.tex', 'flufl.password Documentation', 'Barry A. Warsaw', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True import errno def index_html(): cwd = os.getcwd() try: os.chdir('build/sphinx/html') try: os.symlink('README.html', 'index.html') except OSError as error: if error.errno != errno.EEXIST: raise print('index.html -> README.html') finally: os.chdir(cwd) import atexit atexit.register(index_html) flufl.password-1.3/flufl/password/NEWS.rst0000664000175000017500000000171212410632171021066 0ustar barrybarry00000000000000======================= NEWS for flufl.password ======================= 1.3 (2014-09-24) ================ * Fix documentation bug. (LP: #1026403) * Purge all references to `distribute`. * Describe the switch to git and the repository move. 1.2.1 (2012-04-19) ================== * Add classifiers to setup.py and make the long description more compatible with the Cheeseshop. * Other changes to make the Cheeseshop page look nicer. (LP: #680136) * setup_helper.py version 2.1. 1.2 (2012-01-23) ================ * Fix some packaging issues. * Remove tox.ini. * Bump Copyright years. * Update standard template. * Eliminate the need to use 2to3, and fix some Python 3 deprecations. 1.1.1 (2012-01-01) ================== * Ensure all built-in schemes are registered by importing them in the __init__.py file. 1.1 (2011-12-31) ================ * Add user-friendly password generation API. 1.0 (2011-12-31) ================ * Initial release. flufl.password-1.3/flufl/password/_hash.py0000664000175000017500000000336012410630115021211 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Make secrets.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'make_secret', ] def make_secret(password, scheme=None): """Return a hashed password using the given scheme. :param password: The plain text password to hash. :type password: bytes or utf-8 encoded string. :param scheme: The scheme class to use for encryption. If not given, `SSHAPasswordScheme` is used. :type scheme: PasswordScheme subclass. :return: The hashed password. :rtype: bytes """ if not isinstance(password, bytes): # If it's not a bytes, it better be a unicode in Python 2 or a str in # Python 3. Let encoding errors propagate up. password = password.encode('utf-8') if scheme is None: from flufl.password.schemes import SSHAPasswordScheme scheme = SSHAPasswordScheme secret = scheme.make_secret(password) if not isinstance(scheme.TAG, bytes): tag = scheme.TAG.encode('utf-8') return b'{' + tag + b'}' + secret flufl.password-1.3/flufl/password/README.rst0000664000175000017500000000456112410630274021256 0ustar barrybarry00000000000000================================================== flufl.password - Password hashing and verification ================================================== This package is called ``flufl.password``. It provides for hashing and verification of passwords. Requirements ============ ``flufl.password`` requires Python 2.6 or newer, and is compatible with Python 3. Documentation ============= A `simple guide`_ to using the library is available within this package, in the form of doctests. The manual is also available online in the Cheeseshop at: http://package.python.org/flufl.password Project details =============== The project home page is: http://launchpad.net/flufl.password You should report bugs at: http://bugs.launchpad.net/flufl.password You can download the latest version of the package either from the Cheeseshop: http://pypi.python.org/pypi/flufl.password Of course you can also just install it with ``pip``:: % sudo pip install flufl.password You may want to use `virtualenv`_ instead of installing the package into the system Python. You can grab the latest development copy of the code using Bazaar, from the Launchpad home page above. See http://bazaar-vcs.org for details on the Bazaar distributed revision control system. If you have Bazaar installed, you can grab your own branch of the code like this:: $ git clone git://gitorious.org/flufl-password/flufl-password.git You may contact the author via barry@python.org. Copyright ========= Copyright (C) 2011-2014 Barry A. Warsaw This file is part of flufl.password. flufl.password is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. flufl.password is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with flufl.password. If not, see . Table of Contents ================= .. toctree:: docs/using.rst NEWS.rst .. _`simple guide`: docs/using.html .. _`virtualenv`: http://www.virtualenv.org/en/latest/index.html flufl.password-1.3/flufl/password/_verify.py0000664000175000017500000000331512410627540021602 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Verification.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'verify', ] from flufl.password._registry import lookup from flufl.password._utils import split def verify(hashed, cleartext): """Verify whether the clear text response matches the hashed challenge. :param hashed: The hashed password. :type hashed: bytes :param cleartext: The clear text password to match against. :type cleartext: bytes or utf-8 encoded string. :return: Flag indicating whether the password matched. :rtype: bool :raises BadPasswordSchemeError: when the indicated scheme cannot be found. :raises BadPasswordFormatError: when the hashed parameter is not properly formatted as ``{SCHEME}password``. """ if not isinstance(cleartext, bytes): cleartext = cleartext.encode('utf-8') scheme, secret = split(hashed) scheme_class = lookup(scheme.upper()) return scheme_class.check_response(secret, cleartext, scheme) flufl.password-1.3/flufl/password/tests/0000775000175000017500000000000012410633530020721 5ustar barrybarry00000000000000flufl.password-1.3/flufl/password/tests/test_documentation.py0000664000175000017500000000627412410627540025220 0ustar barrybarry00000000000000# Copyright (C) 2004-2014 by Barry A. Warsaw # # This file is part of flufl.password. # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Test harness for doctests.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'additional_tests', ] import os import atexit import doctest import unittest from pkg_resources import ( resource_filename, resource_exists, resource_listdir, cleanup_resources) COMMASPACE = ', ' DOT = '.' DOCTEST_FLAGS = ( doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF) def stop(): """Call into pdb.set_trace()""" # Do the import here so that you get the wacky special hacked pdb instead # of Python's normal pdb. import pdb pdb.set_trace() def show(thing): # Python 2 and 3 display bytes objects differently, even with print. This # makes it more difficult to write doctests that work in both versions of # Python, because hashed passwords are all bytes. The root cause is that # in Python 2, `bytes` is just an alias for `str` and in Python 3, the # repr of bytes includes the b'' prefix. Use this instead of print() in # the doctests. if isinstance(thing, bytes) and (bytes is not str): # There are lots of ways we could do this. thing = repr(thing)[2:-1] print(thing) def setup(testobj): """Test setup.""" # Make sure future statements in our doctests match the Python code. When # run with 2to3, the future import gets removed and these names are not # defined. try: testobj.globs['absolute_import'] = absolute_import testobj.globs['print_function'] = print_function testobj.globs['unicode_literals'] = unicode_literals except NameError: pass testobj.globs['show'] = show testobj.globs['stop'] = stop def additional_tests(): "Run the doc tests (README.rst and docs/*, if any exist)" doctest_files = [ os.path.abspath(resource_filename('flufl.password', 'README.rst'))] if resource_exists('flufl.password', 'docs'): for name in resource_listdir('flufl.password', 'docs'): if name.endswith('.rst'): doctest_files.append( os.path.abspath( resource_filename('flufl.password', 'docs/%s' % name))) kwargs = dict(module_relative=False, optionflags=DOCTEST_FLAGS, setUp=setup, ) atexit.register(cleanup_resources) return unittest.TestSuite(( doctest.DocFileSuite(*doctest_files, **kwargs))) flufl.password-1.3/flufl/password/tests/test_passwords.py0000664000175000017500000001032712410630510024355 0ustar barrybarry00000000000000# Copyright (C) 2011-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Unit tests for the passwords module.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'TestCleartextPasswords', 'TestNoPasswords', 'TestPBKDF2Passwords', 'TestPasswordGeneration', 'TestSHAPasswords', 'TestSSHAPasswords', 'TestSchemeLookup', ] import unittest # Python 3 does not have the izip form. try: from itertools import izip_longest as longest except ImportError: from itertools import zip_longest as longest from flufl.password._hash import make_secret from flufl.password._registry import BadPasswordSchemeError, lookup from flufl.password._verify import verify from flufl.password import schemes, generate class PasswordsTestBase: scheme = None def setUp(self): # The user's password, as a bytes self.pwbyte = b'abc' self.pwutf8 = 'abc\xc3\xbf' # 'abc\xff' # Bad passwords; bytes self.badbyte = b'def' def test_byte_passwords(self): secret = make_secret(self.pwbyte, self.scheme) self.assertTrue(verify(secret, self.pwbyte), self.scheme) self.assertFalse(verify(secret, self.badbyte), self.scheme) def test_utf8_passwords(self): secret = make_secret(self.pwutf8, self.scheme) self.assertTrue(verify(secret, self.pwutf8), self.scheme) self.assertFalse(verify(secret, self.badbyte), self.scheme) class TestNoPasswords(unittest.TestCase): def test_make_secret(self): self.assertEqual(schemes.NoPasswordScheme.make_secret('whatever'), b'') def test_check_response(self): self.assertFalse( schemes.NoPasswordScheme.check_response(b'foo', 'bar')) self.assertFalse(schemes.NoPasswordScheme.check_response(b'', 'bar')) class TestCleartextPasswords(PasswordsTestBase, unittest.TestCase): scheme = schemes.ClearTextPasswordScheme class TestSHAPasswords(PasswordsTestBase, unittest.TestCase): scheme = schemes.SHAPasswordScheme class TestSSHAPasswords(PasswordsTestBase, unittest.TestCase): scheme = schemes.SSHAPasswordScheme class TestPBKDF2Passwords(PasswordsTestBase, unittest.TestCase): scheme = schemes.PBKDF2PasswordScheme class TestSchemeLookup(unittest.TestCase): def test_scheme_name_lookup(self): self.assertEqual(lookup('NONE'), schemes.NoPasswordScheme) self.assertEqual(lookup('CLEARTEXT'), schemes.ClearTextPasswordScheme) self.assertEqual(lookup('SHA'), schemes.SHAPasswordScheme) self.assertEqual(lookup('SSHA'), schemes.SSHAPasswordScheme) self.assertEqual(lookup('PBKDF2'), schemes.PBKDF2PasswordScheme) def test_lookup_error(self): self.assertRaises(BadPasswordSchemeError, lookup, 'BOGUS') # See itertools doc page examples. def _grouper(seq): args = [iter(seq)] * 2 return list(longest(*args)) class TestPasswordGeneration(unittest.TestCase): def test_provided_user_friendly_password_length(self): self.assertEqual(len(generate(12)), 12) def test_provided_odd_user_friendly_password_length(self): self.assertEqual(len(generate(15)), 15) def test_user_friendly_password(self): password = generate() for pair in _grouper(password): # There will always be one vowel and one non-vowel. vowel = (pair[0] if pair[0] in 'aeiou' else pair[1]) consonant = (pair[0] if pair[0] not in 'aeiou' else pair[1]) self.assertTrue(vowel in 'aeiou', vowel) self.assertTrue(consonant not in 'aeiou', consonant) flufl.password-1.3/flufl/password/tests/__init__.py0000664000175000017500000000000012410627540023024 0ustar barrybarry00000000000000flufl.password-1.3/flufl/password/docs/0000775000175000017500000000000012410633530020507 5ustar barrybarry00000000000000flufl.password-1.3/flufl/password/docs/using.rst0000664000175000017500000001272512410627540022401 0ustar barrybarry00000000000000================================ Using the flufl.password package ================================ This package comes with a number of password hashing schemes. Some are more secure, while others provide for useful debugging. A hashed password follows the syntax promoted in `RFC 2307`_ (as best I can tell), having a basic format of ``{scheme}hashed_password``. Hashing a password ================== You can create a secure hashed password using the default scheme, which includes random data. >>> from flufl.password import make_secret >>> show(make_secret('my password')) {SSHA}... You can also create a hashed password using one of the other built-in schemes. >>> from flufl.password.schemes import SHAPasswordScheme >>> show(make_secret('my password', SHAPasswordScheme)) {SHA}ovj3-hlaCAoipokEHaqPIET58zY= Available schemes ----------------- There are several built-in schemes to choose from, which run the gamut from useful for debugging to variously higher levels of security. * The *no password* scheme throws away the password and always returns the empty string, but with a properly formatted password. >>> from flufl.password.schemes import NoPasswordScheme >>> show(make_secret('my password', NoPasswordScheme)) {NONE} * The *clear text* scheme returns the original password in clear text, but properly formatted. >>> from flufl.password.schemes import ClearTextPasswordScheme >>> show(make_secret('my password', ClearTextPasswordScheme)) {CLEARTEXT}my password * The *SHA1* password scheme encodes the SHA1 hash of the password. >>> show(make_secret('my password', SHAPasswordScheme)) {SHA}ovj3-hlaCAoipokEHaqPIET58zY= * The *salted SHA1* scheme adds a random salt to the password's digest. >>> from flufl.password.schemes import SSHAPasswordScheme >>> show(make_secret('my password', SSHAPasswordScheme)) {SSHA}... * There is an `RFC 2898`_ password encoding scheme. >>> from flufl.password.schemes import PBKDF2PasswordScheme >>> show(make_secret('my password', PBKDF2PasswordScheme)) {PBKDF2 SHA 2000}... Custom schemes -------------- It's also easy enough to create your own scheme. It must implement a static `make_secret()` method, which you can inherit from a common base class. The class must also have a `TAG` attribute which gives the unique name of this hashing scheme. The scheme should be registered so that it can be found by its tag for verification purposes. This can be done using the `@register` descriptor. :: >>> from codecs import getencoder >>> from flufl.password import register >>> from flufl.password.schemes import PasswordScheme >>> @register ... class MyScheme(PasswordScheme): ... TAG = 'CAESAR' ... @staticmethod ... def make_secret(password): ... # In Python 3, this is a string-to-string encoding. The ... # caller already turned `password` into a byte string, so ... # we have to pass it back through a string to rotate it. ... # We also can't just call .encode('rot_13') on the string ... # because Python 3.2 chokes on the returned string (it expects ... # a bytes object to be returned). Sigh. ... as_string = password.decode('utf-8') ... encoder = getencoder('rot_13') ... return encoder(as_string)[0].encode('utf-8') >>> show(make_secret('my password', MyScheme)) {CAESAR}zl cnffjbeq Hashed passwords are always bytes. >>> isinstance(make_secret('my password', MyScheme), bytes) True Verifying a password ==================== When the user entered their original password, you hashed it using one of the schemes mentioned above. You are only storing this hashed password in your database. The user now wants to log in, so she provides you with her plain text password. You want to see if they match. The easiest way to do this is to give both the plain text password the user just typed, and the hash password you have in your database. >>> from flufl.password import verify >>> verify(b'{SHA}ovj3-hlaCAoipokEHaqPIET58zY=', 'my password') True Of course, if they enter the wrong password, it does not verify. >>> verify(b'{SHA}ovj3-hlaCAoipokEHaqPIET58zY=', 'your password') False Your custom hashing scheme must implement the `check_response()` API in order to support password verification. The `PasswordScheme` base class supports the most obvious implementation of this, which serves for most schemes. For example, the Caesar scheme does not need to implement a `check_response()` method. >>> verify(b'{CAESAR}zl cnffjbeq', 'my password') True User-friendly passwords ======================= This package also provides a convenient utility for generating *user friendly* passwords. These passwords gather random input and translate them into pairs of vowel-consonant (or consonant-vowel) syllables. It then strings together enough of these syllables to match the requested password length. In theory, this produces relatively secure passwords that are easier to pronounce and remember. The security claims of these generated passwords have not been evaluated. >>> from flufl.password import generate >>> my_password = generate(10) >>> len(my_password) 10 >>> sum(1 for c in my_password if c in 'aeiou') 5 >>> sum(1 for c in my_password if c not in 'aeiou') 5 .. _`RFC 2307`: http://www.faqs.org/rfcs/rfc2307.html .. _`RFC 2898`: http://www.faqs.org/rfcs/rfc2898.html flufl.password-1.3/flufl/password/docs/__init__.py0000664000175000017500000000000012410627540022612 0ustar barrybarry00000000000000flufl.password-1.3/flufl/__init__.py0000664000175000017500000000160612410627540020035 0ustar barrybarry00000000000000# Copyright (C) 2004-2014 by Barry A. Warsaw # # This file is part of flufl.password. # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . # this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) flufl.password-1.3/setup.cfg0000664000175000017500000000022512410633530016425 0ustar barrybarry00000000000000[build_sphinx] source_dir = flufl/password [upload_docs] upload_dir = build/sphinx/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 flufl.password-1.3/template.py0000664000175000017500000000150112410630050016761 0ustar barrybarry00000000000000# Copyright (C) 2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """Module contents.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ ] flufl.password-1.3/MANIFEST.in0000664000175000017500000000006412410627537016355 0ustar barrybarry00000000000000include *.py global-include *.txt *.rst prune build flufl.password-1.3/README.rst0000664000175000017500000000145312410627537016311 0ustar barrybarry00000000000000============== flufl.password ============== Password hashing and verification. The ``flufl.password`` library provides hashing and verification of passwords. License ======= This file is part of flufl.password. flufl.password is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 of the License. flufl.password is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with flufl.password. If not, see . flufl.password-1.3/flufl.password.egg-info/0000775000175000017500000000000012410633530021250 5ustar barrybarry00000000000000flufl.password-1.3/flufl.password.egg-info/PKG-INFO0000664000175000017500000000665212410633530022356 0ustar barrybarry00000000000000Metadata-Version: 1.1 Name: flufl.password Version: 1.3 Summary: Password hashing and verification. Home-page: http://launchpad.net/flufl.password Author: Barry Warsaw Author-email: barry@python.org License: LGPLv3 Download-URL: https://launchpad.net/flufl.password/+download Description: ============== flufl.password ============== Password hashing and verification. The ``flufl.password`` library provides hashing and verification of passwords. License ======= This file is part of flufl.password. flufl.password is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 of the License. flufl.password is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with flufl.password. If not, see . ======================= NEWS for flufl.password ======================= 1.3 (2014-09-24) ================ * Fix documentation bug. (LP: #1026403) * Purge all references to `distribute`. * Describe the switch to git and the repository move. 1.2.1 (2012-04-19) ================== * Add classifiers to setup.py and make the long description more compatible with the Cheeseshop. * Other changes to make the Cheeseshop page look nicer. (LP: #680136) * setup_helper.py version 2.1. 1.2 (2012-01-23) ================ * Fix some packaging issues. * Remove tox.ini. * Bump Copyright years. * Update standard template. * Eliminate the need to use 2to3, and fix some Python 3 deprecations. 1.1.1 (2012-01-01) ================== * Ensure all built-in schemes are registered by importing them in the __init__.py file. 1.1 (2011-12-31) ================ * Add user-friendly password generation API. 1.0 (2011-12-31) ================ * Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules flufl.password-1.3/flufl.password.egg-info/SOURCES.txt0000664000175000017500000000132112410633530023131 0ustar barrybarry00000000000000MANIFEST.in README.rst setup.cfg setup.py setup_helpers.py template.py flufl/__init__.py flufl.password.egg-info/PKG-INFO flufl.password.egg-info/SOURCES.txt flufl.password.egg-info/dependency_links.txt flufl.password.egg-info/namespace_packages.txt flufl.password.egg-info/top_level.txt flufl/password/NEWS.rst flufl/password/README.rst flufl/password/__init__.py flufl/password/_generate.py flufl/password/_hash.py flufl/password/_registry.py flufl/password/_utils.py flufl/password/_verify.py flufl/password/conf.py flufl/password/schemes.py flufl/password/docs/__init__.py flufl/password/docs/using.rst flufl/password/tests/__init__.py flufl/password/tests/test_documentation.py flufl/password/tests/test_passwords.pyflufl.password-1.3/flufl.password.egg-info/top_level.txt0000664000175000017500000000000612410633530023776 0ustar barrybarry00000000000000flufl flufl.password-1.3/flufl.password.egg-info/namespace_packages.txt0000664000175000017500000000000612410633530025577 0ustar barrybarry00000000000000flufl flufl.password-1.3/flufl.password.egg-info/dependency_links.txt0000664000175000017500000000000112410633530025316 0ustar barrybarry00000000000000 flufl.password-1.3/setup_helpers.py0000664000175000017500000001206212410627537020054 0ustar barrybarry00000000000000# Copyright (C) 2009-2014 by Barry A. Warsaw # # This file is part of flufl.password # # flufl.password is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # flufl.password is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with flufl.password. If not, see . """setup.py helper functions.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'description', 'find_doctests', 'get_version', 'long_description', 'require_python', ] import os import re import sys DEFAULT_VERSION_RE = re.compile(r'(?P\d+\.\d+(?:\.\d+)?)') EMPTYSTRING = '' __version__ = '2.1' def require_python(minimum): """Require at least a minimum Python version. The version number is expressed in terms of `sys.hexversion`. E.g. to require a minimum of Python 2.6, use:: >>> require_python(0x206000f0) :param minimum: Minimum Python version supported. :type minimum: integer """ if sys.hexversion < minimum: hversion = hex(minimum)[2:] if len(hversion) % 2 != 0: hversion = '0' + hversion split = list(hversion) parts = [] while split: parts.append(int(''.join((split.pop(0), split.pop(0))), 16)) major, minor, micro, release = parts if release == 0xf0: print('Python {0}.{1}.{2} or better is required'.format( major, minor, micro)) else: print('Python {0}.{1}.{2} ({3}) or better is required'.format( major, minor, micro, hex(release)[2:])) sys.exit(1) def get_version(filename, pattern=None): """Extract the __version__ from a file without importing it. While you could get the __version__ by importing the module, the very act of importing can cause unintended consequences. For example, Distribute's automatic 2to3 support will break. Instead, this searches the file for a line that starts with __version__, and extract the version number by regular expression matching. By default, two or three dot-separated digits are recognized, but by passing a pattern parameter, you can recognize just about anything. Use the `version` group name to specify the match group. :param filename: The name of the file to search. :type filename: string :param pattern: Optional alternative regular expression pattern to use. :type pattern: string :return: The version that was extracted. :rtype: string """ if pattern is None: cre = DEFAULT_VERSION_RE else: cre = re.compile(pattern) with open(filename) as fp: for line in fp: if line.startswith('__version__'): mo = cre.search(line) assert mo, 'No valid __version__ string found' return mo.group('version') raise AssertionError('No __version__ assignment found') def find_doctests(start='.', extension='.rst'): """Find separate-file doctests in the package. This is useful for Distribute's automatic 2to3 conversion support. The `setup()` keyword argument `convert_2to3_doctests` requires file names, which may be difficult to track automatically as you add new doctests. :param start: Directory to start searching in (default is cwd) :type start: string :param extension: Doctest file extension (default is .txt) :type extension: string :return: The doctest files found. :rtype: list """ doctests = [] for dirpath, dirnames, filenames in os.walk(start): doctests.extend(os.path.join(dirpath, filename) for filename in filenames if filename.endswith(extension)) return doctests def long_description(*filenames): """Provide a long description.""" res = [''] for filename in filenames: with open(filename) as fp: for line in fp: res.append(' ' + line) res.append('') res.append('\n') return EMPTYSTRING.join(res) def description(filename): """Provide a short description.""" # This ends up in the Summary header for PKG-INFO and it should be a # one-liner. It will get rendered on the package page just below the # package version header but above the long_description, which ironically # gets stuff into the Description header. It should not include reST, so # pick out the first single line after the double header. with open(filename) as fp: for lineno, line in enumerate(fp): if lineno < 3: continue line = line.strip() if len(line) > 0: return line