├── .github └── workflows │ ├── publish_to_pypi.yml │ └── run_tests.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── _themes │ ├── LICENSE │ ├── README │ ├── flask │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ │ ├── flasky.css_t │ │ │ └── small_flask.css │ │ └── theme.conf │ ├── flask_small │ │ ├── layout.html │ │ ├── static │ │ │ └── flasky.css_t │ │ └── theme.conf │ └── flask_theme_support.py ├── conf.py ├── docs.zip └── index.rst ├── flask_bcrypt.py ├── setup.py └── test_bcrypt.py /.github/workflows/publish_to_pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build-n-publish: 9 | name: Build and publish Python 🐍 distributions 📦 to PyPI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 3.8 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: '3.8' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install setuptools wheel twine 21 | - name: Build and publish 22 | env: 23 | TWINE_USERNAME: __token__ 24 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 25 | run: | 26 | python setup.py sdist bdist_wheel 27 | twine upload dist/* 28 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: Python tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | unit-tests: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: [3.6, 3.7, 3.8, 3.9] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install the package and dependencies 26 | run: python setup.py install 27 | # - name: Lint with flake8 28 | # run: | 29 | # pip install flake8 30 | # # stop the build if there are Python syntax errors or undefined names 31 | # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Install testing dependencies 35 | run: pip install pytest --upgrade 36 | - name: Test with pytest 37 | run: pytest -s -vv --color=yes 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | # IDEs 141 | .idea/ 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 by Max Countryman. 2 | + (c) 2021 by Suren Khorenyan 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * The names of the contributors may not be used to endorse or 19 | promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Bcrypt 2 | 3 | ## Why Fork? 4 | 5 | [Original repo](https://github.com/maxcountryman/flask-bcrypt) gets some updates at GitHub, but new versions are not uploaded to pypi at all. 6 | Maintainer [declined](https://github.com/maxcountryman/flask-bcrypt/pull/66) to keep pypi project up to date. 7 | So here's this repo: [PyPI package](https://pypi.org/project/Bcrypt-Flask/) is automatically updated and is in sync with the [master branch](https://github.com/mahenzon/flask-bcrypt). 8 | 9 | ## Description 10 | 11 | Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for 12 | your application. 13 | 14 | Due to the recent increased prevalence of powerful hardware, such as modern 15 | GPUs, hashes have become increasingly easy to crack. A proactive solution to 16 | this is to use a hash that was designed to be "de-optimized". Bcrypt is such 17 | a hashing facility; unlike hashing algorithms such as MD5 and SHA1, which are 18 | optimized for speed, bcrypt is intentionally structured to be slow. 19 | 20 | For sensitive data that must be protected, such as passwords, bcrypt is an 21 | advisable choice. 22 | 23 | ## Installation 24 | 25 | Install using pip: 26 | 27 | ```bash 28 | pip install Bcrypt-Flask 29 | ``` 30 | 31 | ## Usage 32 | 33 | To use the extension simply import the class wrapper and pass the Flask app 34 | object back to here. Do so like this: 35 | 36 | ```python 37 | from flask import Flask 38 | from flask_bcrypt import Bcrypt 39 | 40 | app = Flask(__name__) 41 | bcrypt = Bcrypt(app) 42 | ``` 43 | 44 | Two primary hashing methods are now exposed by way of the bcrypt object. Use 45 | them like so: 46 | 47 | ```python 48 | pw_hash = bcrypt.generate_password_hash('hunter2') 49 | bcrypt.check_password_hash(pw_hash, 'hunter2') # returns True 50 | ``` 51 | 52 | ## Configuration 53 | 54 | (Flask config) 55 | 56 | - `BCRYPT_LOG_ROUNDS`: default `12` 57 | - `BCRYPT_HASH_PREFIX`: default `'2b'` 58 | - `BCRYPT_HANDLE_LONG_PASSWORDS`: default `False`. 59 | By default, the bcrypt algorithm has a maximum password length of 72 bytes 60 | and ignores any bytes beyond that. A common workaround is to hash the 61 | given password using a cryptographic hash (such as `sha256`), take its 62 | hexdigest to prevent NULL byte problems, and hash the result with bcrypt. 63 | If the `BCRYPT_HANDLE_LONG_PASSWORDS` configuration value is set to `True`, 64 | the workaround described above will be enabled. 65 | **Warning: do not enable this option on a project that is already using 66 | Flask-Bcrypt, or you will break password checking.** 67 | **Warning: if this option is enabled on an existing project, disabling it 68 | will break password checking.** 69 | 70 | ## Documentation 71 | 72 | https://bcrypt-flask.readthedocs.io/en/latest/ 73 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-Bcrypt.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Bcrypt.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Bcrypt" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Bcrypt" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /docs/_themes/README: -------------------------------------------------------------------------------- 1 | Flask Sphinx Styles 2 | =================== 3 | 4 | This repository contains sphinx styles for Flask and Flask related 5 | projects. To use this style in your Sphinx documentation, follow 6 | this guide: 7 | 8 | 1. put this folder as _themes into your docs folder. Alternatively 9 | you can also use git submodules to check out the contents there. 10 | 2. add this to your conf.py: 11 | 12 | sys.path.append(os.path.abspath('_themes')) 13 | html_theme_path = ['_themes'] 14 | html_theme = 'flask' 15 | 16 | The following themes exist: 17 | 18 | - 'flask' - the standard flask documentation theme for large 19 | projects 20 | - 'flask_small' - small one-page theme. Intended to be used by 21 | very small addon libraries for flask. 22 | 23 | The following options exist for the flask_small theme: 24 | 25 | [options] 26 | index_logo = '' filename of a picture in _static 27 | to be used as replacement for the 28 | h1 in the index.rst file. 29 | index_logo_height = 120px height of the index logo 30 | github_fork = '' repository name on github for the 31 | "fork me" badge 32 | -------------------------------------------------------------------------------- /docs/_themes/flask/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 16 | {%- endblock %} 17 | -------------------------------------------------------------------------------- /docs/_themes/flask/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_themes/flask/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 18px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #444; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body h1, 154 | div.body h2, 155 | div.body h3, 156 | div.body h4, 157 | div.body h5, 158 | div.body h6 { 159 | font-family: 'Garamond', 'Georgia', serif; 160 | font-weight: normal; 161 | margin: 30px 0px 10px 0px; 162 | padding: 0; 163 | } 164 | 165 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 166 | div.body h2 { font-size: 180%; } 167 | div.body h3 { font-size: 150%; } 168 | div.body h4 { font-size: 130%; } 169 | div.body h5 { font-size: 100%; } 170 | div.body h6 { font-size: 100%; } 171 | 172 | a.headerlink { 173 | color: #ddd; 174 | padding: 0 4px; 175 | text-decoration: none; 176 | } 177 | 178 | a.headerlink:hover { 179 | color: #444; 180 | background: #eaeaea; 181 | } 182 | 183 | div.body p, div.body dd, div.body li { 184 | line-height: 1.4em; 185 | } 186 | 187 | div.admonition { 188 | background: #fafafa; 189 | margin: 20px -30px; 190 | padding: 10px 30px; 191 | border-top: 1px solid #ccc; 192 | border-bottom: 1px solid #ccc; 193 | } 194 | 195 | div.admonition tt.xref, div.admonition a tt { 196 | border-bottom: 1px solid #fafafa; 197 | } 198 | 199 | dd div.admonition { 200 | margin-left: -60px; 201 | padding-left: 60px; 202 | } 203 | 204 | div.admonition p.admonition-title { 205 | font-family: 'Garamond', 'Georgia', serif; 206 | font-weight: normal; 207 | font-size: 24px; 208 | margin: 0 0 10px 0; 209 | padding: 0; 210 | line-height: 1; 211 | } 212 | 213 | div.admonition p.last { 214 | margin-bottom: 0; 215 | } 216 | 217 | div.highlight { 218 | background-color: white; 219 | } 220 | 221 | dt:target, .highlight { 222 | background: #FAF3E8; 223 | } 224 | 225 | div.note { 226 | background-color: #eee; 227 | border: 1px solid #ccc; 228 | } 229 | 230 | div.seealso { 231 | background-color: #ffc; 232 | border: 1px solid #ff6; 233 | } 234 | 235 | div.topic { 236 | background-color: #eee; 237 | } 238 | 239 | p.admonition-title { 240 | display: inline; 241 | } 242 | 243 | p.admonition-title:after { 244 | content: ":"; 245 | } 246 | 247 | pre, tt { 248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 249 | font-size: 0.9em; 250 | } 251 | 252 | img.screenshot { 253 | } 254 | 255 | tt.descname, tt.descclassname { 256 | font-size: 0.95em; 257 | } 258 | 259 | tt.descname { 260 | padding-right: 0.08em; 261 | } 262 | 263 | img.screenshot { 264 | -moz-box-shadow: 2px 2px 4px #eee; 265 | -webkit-box-shadow: 2px 2px 4px #eee; 266 | box-shadow: 2px 2px 4px #eee; 267 | } 268 | 269 | table.docutils { 270 | border: 1px solid #888; 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils td, table.docutils th { 277 | border: 1px solid #888; 278 | padding: 0.25em 0.7em; 279 | } 280 | 281 | table.field-list, table.footnote { 282 | border: none; 283 | -moz-box-shadow: none; 284 | -webkit-box-shadow: none; 285 | box-shadow: none; 286 | } 287 | 288 | table.footnote { 289 | margin: 15px 0; 290 | width: 100%; 291 | border: 1px solid #eee; 292 | background: #fdfdfd; 293 | font-size: 0.9em; 294 | } 295 | 296 | table.footnote + table.footnote { 297 | margin-top: -15px; 298 | border-top: none; 299 | } 300 | 301 | table.field-list th { 302 | padding: 0 0.8em 0 0; 303 | } 304 | 305 | table.field-list td { 306 | padding: 0; 307 | } 308 | 309 | table.footnote td.label { 310 | width: 0px; 311 | padding: 0.3em 0 0.3em 0.5em; 312 | } 313 | 314 | table.footnote td { 315 | padding: 0.3em 0.5em; 316 | } 317 | 318 | dl { 319 | margin: 0; 320 | padding: 0; 321 | } 322 | 323 | dl dd { 324 | margin-left: 30px; 325 | } 326 | 327 | blockquote { 328 | margin: 0 0 0 30px; 329 | padding: 0; 330 | } 331 | 332 | ul, ol { 333 | margin: 10px 0 10px 30px; 334 | padding: 0; 335 | } 336 | 337 | pre { 338 | background: #eee; 339 | padding: 7px 30px; 340 | margin: 15px -30px; 341 | line-height: 1.3em; 342 | } 343 | 344 | dl pre, blockquote pre, li pre { 345 | margin-left: -60px; 346 | padding-left: 60px; 347 | } 348 | 349 | dl dl pre { 350 | margin-left: -90px; 351 | padding-left: 90px; 352 | } 353 | 354 | tt { 355 | background-color: #ecf0f3; 356 | color: #222; 357 | /* padding: 1px 2px; */ 358 | } 359 | 360 | tt.xref, a tt { 361 | background-color: #FBFBFB; 362 | border-bottom: 1px solid white; 363 | } 364 | 365 | a.reference { 366 | text-decoration: none; 367 | border-bottom: 1px dotted #004B6B; 368 | } 369 | 370 | a.reference:hover { 371 | border-bottom: 1px solid #6D4100; 372 | } 373 | 374 | a.footnote-reference { 375 | text-decoration: none; 376 | font-size: 0.7em; 377 | vertical-align: top; 378 | border-bottom: 1px dotted #004B6B; 379 | } 380 | 381 | a.footnote-reference:hover { 382 | border-bottom: 1px solid #6D4100; 383 | } 384 | 385 | a:hover tt { 386 | background: #EEE; 387 | } 388 | -------------------------------------------------------------------------------- /docs/_themes/flask/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/_themes/flask/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Flask-Bcrypt documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Dec 14 21:28:26 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.append(os.path.join(os.path.dirname(__file__), '_themes')) 20 | 21 | module_path = os.path.join(os.path.dirname(__file__), '../flask_bcrypt.py') 22 | with open(module_path) as module: 23 | for line in module: 24 | if line.startswith('__version_info__'): 25 | package_version = '.'.join(eval(line.split('__version_info__ = ')[-1])) 26 | break 27 | 28 | # -- General configuration ----------------------------------------------------- 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | #needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be extensions 34 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ['sphinx.ext.autodoc'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = 'Flask-Bcrypt' 51 | copyright = '2021, Suren Khorenyan' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = package_version 59 | # The full version, including alpha/beta/rc tags. 60 | release = package_version 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all documents. 77 | #default_role = None 78 | 79 | # If true, '()' will be appended to :func: etc. cross-reference text. 80 | #add_function_parentheses = True 81 | 82 | # If true, the current module name will be prepended to all description 83 | # unit titles (such as .. function::). 84 | #add_module_names = True 85 | 86 | # If true, sectionauthor and moduleauthor directives will be shown in the 87 | # output. They are ignored by default. 88 | #show_authors = False 89 | 90 | # The name of the Pygments (syntax highlighting) style to use. 91 | pygments_style = 'sphinx' 92 | 93 | # A list of ignored prefixes for module index sorting. 94 | #modindex_common_prefix = [] 95 | 96 | 97 | # -- Options for HTML output --------------------------------------------------- 98 | html_theme_options = {'github_fork': 'mahenzon/flask-bcrypt', 'index_logo': False} 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'flask_small' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | html_theme_path = ['_themes'] 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | #html_theme_path = [] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 137 | # using the given strftime format. 138 | #html_last_updated_fmt = '%b %d, %Y' 139 | 140 | # If true, SmartyPants will be used to convert quotes and dashes to 141 | # typographically correct entities. 142 | #html_use_smartypants = True 143 | 144 | # Custom sidebar templates, maps document names to template names. 145 | #html_sidebars = {} 146 | 147 | # Additional templates that should be rendered to pages, maps page names to 148 | # template names. 149 | #html_additional_pages = {} 150 | 151 | # If false, no module index is generated. 152 | #html_domain_indices = True 153 | 154 | # If false, no index is generated. 155 | #html_use_index = True 156 | 157 | # If true, the index is split into individual pages for each letter. 158 | #html_split_index = False 159 | 160 | # If true, links to the reST sources are added to the pages. 161 | #html_show_sourcelink = True 162 | 163 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 164 | #html_show_sphinx = True 165 | 166 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 167 | #html_show_copyright = True 168 | 169 | # If true, an OpenSearch description file will be output, and all pages will 170 | # contain a tag referring to it. The value of this option must be the 171 | # base URL from which the finished HTML is served. 172 | #html_use_opensearch = '' 173 | 174 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 175 | #html_file_suffix = None 176 | 177 | # Output file base name for HTML help builder. 178 | htmlhelp_basename = 'Flask-Bcryptdoc' 179 | 180 | 181 | # -- Options for LaTeX output -------------------------------------------------- 182 | 183 | # The paper size ('letter' or 'a4'). 184 | #latex_paper_size = 'letter' 185 | 186 | # The font size ('10pt', '11pt' or '12pt'). 187 | #latex_font_size = '10pt' 188 | 189 | # Grouping the document tree into LaTeX files. List of tuples 190 | # (source start file, target name, title, author, documentclass [howto/manual]). 191 | latex_documents = [ 192 | ('index', 'Flask-Bcrypt.tex', 'Flask-Bcrypt Documentation', 193 | 'Suren Khorenyan', 'manual'), 194 | ] 195 | 196 | # The name of an image file (relative to this directory) to place at the top of 197 | # the title page. 198 | #latex_logo = None 199 | 200 | # For "manual" documents, if this is true, then toplevel headings are parts, 201 | # not chapters. 202 | #latex_use_parts = False 203 | 204 | # If true, show page references after internal links. 205 | #latex_show_pagerefs = False 206 | 207 | # If true, show URL addresses after external links. 208 | #latex_show_urls = False 209 | 210 | # Additional stuff for the LaTeX preamble. 211 | #latex_preamble = '' 212 | 213 | # Documents to append as an appendix to all manuals. 214 | #latex_appendices = [] 215 | 216 | # If false, no module index is generated. 217 | #latex_domain_indices = True 218 | 219 | 220 | # -- Options for manual page output -------------------------------------------- 221 | 222 | # One entry per manual page. List of tuples 223 | # (source start file, name, description, authors, manual section). 224 | man_pages = [ 225 | ('index', 'flask-bcrypt', 'Flask-Bcrypt Documentation', 226 | ['Suren Khorenyan'], 1) 227 | ] 228 | -------------------------------------------------------------------------------- /docs/docs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahenzon/flask-bcrypt/3276f3698dfc6bcc942cc94d8faa6dac4894778f/docs/docs.zip -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Flask-Bcrypt 2 | ============= 3 | 4 | .. module:: flask_bcrypt 5 | 6 | Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for 7 | your application. 8 | 9 | Due to the recent increased prevalence of powerful hardware, such as modern 10 | GPUs, hashes have become increasingly easy to crack. A proactive solution to 11 | this is to use a hash that was designed to be "de-optimized". Bcrypt is such 12 | a hashing facility; unlike hashing algorithms such as MD5 and SHA1, which are 13 | optimized for speed, bcrypt is intentionally structured to be slow. 14 | 15 | For sensitive data that must be protected, such as passwords, bcrypt is an 16 | advisable choice. 17 | 18 | .. _Flask-Bcrypt: http://github.com/mahenzon/flask-bcrypt 19 | .. _Flask: http://flask.pocoo.org/ 20 | 21 | Installation 22 | ------------ 23 | 24 | Install using pip: 25 | 26 | $ pip install Bcrypt-Flask 27 | 28 | .. note:: 29 | You need Python Development Headers to install py-bcrypt package, needed 30 | as a dependency. If you are on Mac OS or Windows, you probably have it 31 | already installed. Otherwise look for python-dev package for Debian-based 32 | distributives and for python-devel package for RedHat-based. 33 | 34 | Usage 35 | ----- 36 | 37 | To use the extension simply import the class wrapper and pass the Flask app 38 | object back to here. Do so like this:: 39 | 40 | from flask import Flask 41 | from flask_bcrypt import Bcrypt 42 | 43 | app = Flask(__name__) 44 | bcrypt = Bcrypt(app) 45 | 46 | Two primary hashing methods are now exposed by way of the bcrypt object. In Python 2, use 47 | them like so:: 48 | 49 | pw_hash = bcrypt.generate_password_hash('hunter2') 50 | bcrypt.check_password_hash(pw_hash, 'hunter2') # returns True 51 | 52 | In Python 3, you need to use decode('utf-8') on generate_password_hash(), like below: 53 | 54 | pw_hash = bcrypt.generate_password_hash('hunter2').decode('utf-8') 55 | 56 | API 57 | ___ 58 | .. autoclass:: flask_bcrypt.Bcrypt 59 | :members: 60 | 61 | .. autofunction:: flask_bcrypt.generate_password_hash 62 | 63 | .. autofunction:: flask_bcrypt.check_password_hash 64 | 65 | 66 | Configuration 67 | _____________ 68 | 69 | (Flask config) 70 | 71 | - ``BCRYPT_LOG_ROUNDS``: default ``12`` 72 | - ``BCRYPT_HASH_PREFIX``: default ``'2b'`` 73 | - ``BCRYPT_HANDLE_LONG_PASSWORDS``: default ``False``. 74 | By default, the bcrypt algorithm has a maximum password length of 72 75 | bytes 76 | and ignores any bytes beyond that. A common workaround is to hash the 77 | given password using a cryptographic hash (such as ``sha256``), take 78 | its 79 | hexdigest to prevent NULL byte problems, and hash the result with 80 | bcrypt. 81 | If the ``BCRYPT_HANDLE_LONG_PASSWORDS`` configuration value is set to 82 | ``True``, 83 | the workaround described above will be enabled. 84 | **Warning: do not enable this option on a project that is already 85 | using 86 | Flask-Bcrypt, or you will break password checking.** 87 | **Warning: if this option is enabled on an existing project, 88 | disabling it 89 | will break password checking.** 90 | -------------------------------------------------------------------------------- /flask_bcrypt.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Flask extension providing bcrypt hashing and comparison facilities. 3 | 4 | :copyright: (c) 2011 by Max Countryman. 5 | :copyright: (c) 2021 by Suren Khorenyan. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import print_function 11 | 12 | __version_info__ = ('1', '0', '2') 13 | __version__ = '.'.join(__version_info__) 14 | __author__ = 'Suren Khorenyan' 15 | __license__ = 'BSD' 16 | __copyright__ = '(c) 2011 by Max Countryman, (c) 2021 by Suren Khorenyan' 17 | __all__ = ['Bcrypt', 'check_password_hash', 'generate_password_hash'] 18 | 19 | import hmac 20 | 21 | try: 22 | import bcrypt 23 | except ImportError as e: 24 | print('bcrypt is required to use Flask-Bcrypt') 25 | raise e 26 | 27 | import hashlib 28 | 29 | from sys import version_info 30 | 31 | PY3 = version_info[0] >= 3 32 | 33 | 34 | def generate_password_hash(password, rounds=None): 35 | '''This helper function wraps the eponymous method of :class:`Bcrypt`. It 36 | is intended to be used as a helper function at the expense of the 37 | configuration variable provided when passing back the app object. In other 38 | words this shortcut does not make use of the app object at all. 39 | 40 | To use this function, simply import it from the module and use it in a 41 | similar fashion as the original method would be used. Here is a quick 42 | example:: 43 | 44 | from flask_bcrypt import generate_password_hash 45 | pw_hash = generate_password_hash('hunter2', 10) 46 | 47 | :param password: The password to be hashed. 48 | :param rounds: The optional number of rounds. 49 | ''' 50 | return Bcrypt().generate_password_hash(password, rounds) 51 | 52 | 53 | def check_password_hash(pw_hash, password): 54 | '''This helper function wraps the eponymous method of :class:`Bcrypt.` It 55 | is intended to be used as a helper function at the expense of the 56 | configuration variable provided when passing back the app object. In other 57 | words this shortcut does not make use of the app object at all. 58 | 59 | To use this function, simply import it from the module and use it in a 60 | similar fashion as the original method would be used. Here is a quick 61 | example:: 62 | 63 | from flask_bcrypt import check_password_hash 64 | check_password_hash(pw_hash, 'hunter2') # returns True 65 | 66 | :param pw_hash: The hash to be compared against. 67 | :param password: The password to compare. 68 | ''' 69 | return Bcrypt().check_password_hash(pw_hash, password) 70 | 71 | 72 | class Bcrypt(object): 73 | '''Bcrypt class container for password hashing and checking logic using 74 | bcrypt, of course. This class may be used to intialize your Flask app 75 | object. The purpose is to provide a simple interface for overriding 76 | Werkzeug's built-in password hashing utilities. 77 | 78 | Although such methods are not actually overriden, the API is intentionally 79 | made similar so that existing applications which make use of the previous 80 | hashing functions might be easily adapted to the stronger facility of 81 | bcrypt. 82 | 83 | To get started you will wrap your application's app object something like 84 | this:: 85 | 86 | app = Flask(__name__) 87 | bcrypt = Bcrypt(app) 88 | 89 | Now the two primary utility methods are exposed via this object, `bcrypt`. 90 | So in the context of the application, important data, such as passwords, 91 | could be hashed using this syntax:: 92 | 93 | password = 'hunter2' 94 | pw_hash = bcrypt.generate_password_hash(password) 95 | 96 | Once hashed, the value is irreversible. However in the case of validating 97 | logins a simple hashing of candidate password and subsequent comparison. 98 | Importantly a comparison should be done in constant time. This helps 99 | prevent timing attacks. A simple utility method is provided for this:: 100 | 101 | candidate = 'secret' 102 | bcrypt.check_password_hash(pw_hash, candidate) 103 | 104 | If both the candidate and the existing password hash are a match 105 | `check_password_hash` returns True. Otherwise, it returns False. 106 | 107 | .. admonition:: Namespacing Issues 108 | 109 | It's worth noting that if you use the format, `bcrypt = Bcrypt(app)` 110 | you are effectively overriding the bcrypt module. Though it's unlikely 111 | you would need to access the module outside of the scope of the 112 | extension be aware that it's overriden. 113 | 114 | Alternatively consider using a different name, such as `flask_bcrypt 115 | = Bcrypt(app)` to prevent naming collisions. 116 | 117 | Additionally a configuration value for `BCRYPT_LOG_ROUNDS` may be set in 118 | the configuration of the Flask app. If none is provided this will 119 | internally be assigned to 12. (This value is used in determining the 120 | complexity of the encryption, see bcrypt for more details.) 121 | 122 | You may also set the hash version using the `BCRYPT_HASH_PREFIX` field in 123 | the configuration of the Flask app. If not set, this will default to `2b`. 124 | (See bcrypt for more details) 125 | 126 | By default, the bcrypt algorithm has a maximum password length of 72 bytes 127 | and ignores any bytes beyond that. A common workaround is to hash the 128 | given password using a cryptographic hash (such as `sha256`), take its 129 | hexdigest to prevent NULL byte problems, and hash the result with bcrypt. 130 | If the `BCRYPT_HANDLE_LONG_PASSWORDS` configuration value is set to `True`, 131 | the workaround described above will be enabled. 132 | **Warning: do not enable this option on a project that is already using 133 | Flask-Bcrypt, or you will break password checking.** 134 | **Warning: if this option is enabled on an existing project, disabling it 135 | will break password checking.** 136 | 137 | :param app: The Flask application object. Defaults to None. 138 | ''' 139 | 140 | _log_rounds = 12 141 | _prefix = '2b' 142 | _handle_long_passwords = False 143 | 144 | def __init__(self, app=None): 145 | if app is not None: 146 | self.init_app(app) 147 | 148 | def init_app(self, app): 149 | '''Initalizes the application with the extension. 150 | 151 | :param app: The Flask application object. 152 | ''' 153 | self._log_rounds = app.config.get('BCRYPT_LOG_ROUNDS', 12) 154 | self._prefix = app.config.get('BCRYPT_HASH_PREFIX', '2b') 155 | self._handle_long_passwords = app.config.get( 156 | 'BCRYPT_HANDLE_LONG_PASSWORDS', False) 157 | 158 | def _unicode_to_bytes(self, unicode_string): 159 | '''Converts a unicode string to a bytes object. 160 | 161 | :param unicode_string: The unicode string to convert.''' 162 | if PY3: 163 | if isinstance(unicode_string, str): 164 | bytes_object = bytes(unicode_string, 'utf-8') 165 | else: 166 | bytes_object = unicode_string 167 | else: 168 | if isinstance(unicode_string, unicode): 169 | bytes_object = unicode_string.encode('utf-8') 170 | else: 171 | bytes_object = unicode_string 172 | return bytes_object 173 | 174 | def generate_password_hash(self, password, rounds=None, prefix=None): 175 | '''Generates a password hash using bcrypt. Specifying `rounds` 176 | sets the log_rounds parameter of `bcrypt.gensalt()` which determines 177 | the complexity of the salt. 12 is the default value. Specifying `prefix` 178 | sets the `prefix` parameter of `bcrypt.gensalt()` which determines the 179 | version of the algorithm used to create the hash. 180 | 181 | Example usage of :class:`generate_password_hash` might look something 182 | like this:: 183 | 184 | pw_hash = bcrypt.generate_password_hash('secret', 10) 185 | 186 | :param password: The password to be hashed. 187 | :param rounds: The optional number of rounds. 188 | :param prefix: The algorithm version to use. 189 | ''' 190 | 191 | if not password: 192 | raise ValueError('Password must be non-empty.') 193 | 194 | if rounds is None: 195 | rounds = self._log_rounds 196 | if prefix is None: 197 | prefix = self._prefix 198 | 199 | # Python 3 unicode strings must be encoded as bytes before hashing. 200 | password = self._unicode_to_bytes(password) 201 | prefix = self._unicode_to_bytes(prefix) 202 | 203 | if self._handle_long_passwords: 204 | password = hashlib.sha256(password).hexdigest() 205 | password = self._unicode_to_bytes(password) 206 | 207 | salt = bcrypt.gensalt(rounds=rounds, prefix=prefix) 208 | return bcrypt.hashpw(password, salt) 209 | 210 | def check_password_hash(self, pw_hash, password): 211 | '''Tests a password hash against a candidate password. The candidate 212 | password is first hashed and then subsequently compared in constant 213 | time to the existing hash. This will either return `True` or `False`. 214 | 215 | Example usage of :class:`check_password_hash` would look something 216 | like this:: 217 | 218 | pw_hash = bcrypt.generate_password_hash('secret', 10) 219 | bcrypt.check_password_hash(pw_hash, 'secret') # returns True 220 | 221 | :param pw_hash: The hash to be compared against. 222 | :param password: The password to compare. 223 | ''' 224 | 225 | # Python 3 unicode strings must be encoded as bytes before hashing. 226 | pw_hash = self._unicode_to_bytes(pw_hash) 227 | password = self._unicode_to_bytes(password) 228 | 229 | if self._handle_long_passwords: 230 | password = hashlib.sha256(password).hexdigest() 231 | password = self._unicode_to_bytes(password) 232 | 233 | return hmac.compare_digest(bcrypt.hashpw(password, pw_hash), pw_hash) 234 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-Bcrypt 3 | ------------ 4 | 5 | Bcrypt hashing for your Flask. 6 | """ 7 | 8 | import os 9 | 10 | from setuptools import setup 11 | 12 | current_dir = os.path.dirname(__file__) 13 | 14 | 15 | def get_version_line(): 16 | module_path = os.path.join(current_dir, 'flask_bcrypt.py') 17 | with open(module_path) as module: 18 | for line in module: 19 | if line.startswith('__version_info__'): 20 | return line 21 | 22 | 23 | version_line = get_version_line() 24 | __version__ = '.'.join(eval(version_line.split('__version_info__ = ')[-1])) 25 | 26 | 27 | def get_description(): 28 | """ 29 | Read full description from 'README.md' 30 | :return: description 31 | :rtype: str 32 | """ 33 | readme_path = os.path.join(current_dir, 'README.md') 34 | with open(readme_path, 'r', encoding='utf-8') as f: 35 | return f.read() 36 | 37 | 38 | setup( 39 | name='Bcrypt-Flask', 40 | version=__version__, 41 | url='https://github.com/mahenzon/flask-bcrypt', 42 | license='BSD', 43 | author='Suren Khorenyan', 44 | author_email='surenkhorenyan@gmail.com', 45 | description='Brcrypt hashing for Flask.', 46 | long_description=get_description(), 47 | long_description_content_type='text/markdown', 48 | py_modules=['flask_bcrypt'], 49 | zip_safe=False, 50 | platforms='any', 51 | install_requires=['Flask', 'bcrypt>=3.1.1'], 52 | classifiers=[ 53 | 'Environment :: Web Environment', 54 | 'Intended Audience :: Developers', 55 | 'License :: OSI Approved :: BSD License', 56 | 'Operating System :: OS Independent', 57 | 'Programming Language :: Python', 58 | 'Programming Language :: Python :: 2', 59 | 'Programming Language :: Python :: 3', 60 | 'Programming Language :: Python :: 3.4', 61 | 'Programming Language :: Python :: 3.5', 62 | 'Programming Language :: Python :: 3.6', 63 | 'Programming Language :: Python :: 3.7', 64 | 'Programming Language :: Python :: 3.8', 65 | 'Programming Language :: Python :: 3.9', 66 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 67 | 'Topic :: Software Development :: Libraries :: Python Modules' 68 | ], 69 | test_suite='test_bcrypt', 70 | ) 71 | -------------------------------------------------------------------------------- /test_bcrypt.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import unittest 3 | 4 | import flask 5 | from flask_bcrypt import (Bcrypt, 6 | check_password_hash, 7 | generate_password_hash, 8 | PY3) 9 | 10 | 11 | class BasicTestCase(unittest.TestCase): 12 | 13 | def setUp(self): 14 | app = flask.Flask(__name__) 15 | app.config['BCRYPT_LOG_ROUNDS'] = 6 16 | app.config['BCRYPT_HASH_IDENT'] = '2b' 17 | app.config['BCRYPT_HANDLE_LONG_PASSWORDS'] = False 18 | self.bcrypt = Bcrypt(app) 19 | 20 | def test_is_string(self): 21 | pw_hash = self.bcrypt.generate_password_hash('secret') 22 | if PY3: 23 | self.assertTrue(isinstance(pw_hash, bytes)) 24 | else: 25 | self.assertTrue(isinstance(pw_hash, str)) 26 | 27 | def test_custom_rounds(self): 28 | password = 'secret' 29 | pw_hash1 = self.bcrypt.generate_password_hash(password, 5) 30 | self.assertNotEqual(password, pw_hash1) 31 | 32 | def test_check_hash(self): 33 | pw_hash = self.bcrypt.generate_password_hash('secret') 34 | # check a correct password 35 | self.assertTrue(self.bcrypt.check_password_hash(pw_hash, 'secret')) 36 | # check an incorrect password 37 | self.assertFalse(self.bcrypt.check_password_hash(pw_hash, 'hunter2')) 38 | # check unicode 39 | pw_hash = self.bcrypt.generate_password_hash(u'\u2603') 40 | self.assertTrue(self.bcrypt.check_password_hash(pw_hash, u'\u2603')) 41 | # check helpers 42 | pw_hash = generate_password_hash('hunter2') 43 | self.assertTrue(check_password_hash(pw_hash, 'hunter2')) 44 | 45 | def test_check_hash_unicode_is_utf8(self): 46 | password = u'\u2603' 47 | pw_hash = self.bcrypt.generate_password_hash(password) 48 | # check a correct password 49 | self.assertTrue(self.bcrypt.check_password_hash(pw_hash, b'\xe2\x98\x83')) 50 | 51 | def test_rounds_set(self): 52 | self.assertEqual(self.bcrypt._log_rounds, 6) 53 | 54 | def test_unicode_hash(self): 55 | password = u'東京' 56 | h = generate_password_hash(password).decode('utf-8') 57 | self.assertTrue(check_password_hash(h, password)) 58 | 59 | def test_long_password(self): 60 | """Test bcrypt maximum password length. 61 | 62 | The bcrypt algorithm has a maximum password length of 72 bytes, and 63 | ignores any bytes beyond that.""" 64 | 65 | # Create a password with a 72 bytes length 66 | password = 'A' * 72 67 | pw_hash = self.bcrypt.generate_password_hash(password) 68 | # Ensure that a longer password yields the same hash 69 | self.assertTrue(self.bcrypt.check_password_hash(pw_hash, 'A' * 80)) 70 | 71 | 72 | class LongPasswordsTestCase(BasicTestCase): 73 | 74 | def setUp(self): 75 | app = flask.Flask(__name__) 76 | app.config['BCRYPT_LOG_ROUNDS'] = 6 77 | app.config['BCRYPT_HASH_IDENT'] = '2b' 78 | app.config['BCRYPT_HANDLE_LONG_PASSWORDS'] = True 79 | self.bcrypt = Bcrypt(app) 80 | 81 | def test_long_password(self): 82 | """Test the work around bcrypt maximum password length.""" 83 | 84 | # Create a password with a 72 bytes length 85 | password = 'A' * 72 86 | pw_hash = self.bcrypt.generate_password_hash(password) 87 | # Ensure that a longer password **do not** yield the same hash 88 | self.assertFalse(self.bcrypt.check_password_hash(pw_hash, 'A' * 80)) 89 | 90 | 91 | if __name__ == '__main__': 92 | unittest.main() 93 | --------------------------------------------------------------------------------