Codebase list python-flask-httpauth / 7273a04
Import upstream version 4.4.0 Debian Janitor 2 years ago
22 changed file(s) with 1154 addition(s) and 373 deletion(s). Raw diff Collapse all Expand all
0 github: miguelgrinberg
1 patreon: miguelgrinberg
2 custom: https://paypal.me/miguelgrinberg
0 name: build
1 on:
2 push:
3 branches:
4 - master
5 pull_request:
6 branches:
7 - master
8 jobs:
9 lint:
10 name: lint
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v2
14 - uses: actions/setup-python@v2
15 - run: python -m pip install --upgrade pip wheel
16 - run: pip install tox tox-gh-actions
17 - run: tox -eflake8
18 - run: tox -edocs
19 tests:
20 name: tests
21 strategy:
22 matrix:
23 os: [ubuntu-latest, macos-latest, windows-latest]
24 python: ['3.6', '3.7', '3.8', '3.9', 'pypy3']
25 exclude:
26 # pypy3 currently fails to run on Windows
27 - os: windows-latest
28 python: pypy3
29 fail-fast: false
30 runs-on: ${{ matrix.os }}
31 steps:
32 - uses: actions/checkout@v2
33 - uses: actions/setup-python@v2
34 with:
35 python-version: ${{ matrix.python }}
36 - run: python -m pip install --upgrade pip wheel
37 - run: pip install tox tox-gh-actions
38 - run: tox
39 coverage:
40 name: coverage
41 runs-on: ubuntu-latest
42 steps:
43 - uses: actions/checkout@v2
44 - uses: actions/setup-python@v2
45 - run: python -m pip install --upgrade pip wheel
46 - run: pip install tox tox-gh-actions codecov
47 - run: tox
48 - run: codecov
99 build
1010 eggs
1111 parts
12 bin
1312 var
1413 sdist
1514 develop-eggs
0 dist: xenial
01 language: python
12 matrix:
23 include:
3 - python: 3.6
4 - python: 3.8
45 env: TOXENV=flake8
56 - python: 2.7
67 env: TOXENV=py27
7 - python: 3.4
8 env: TOXENV=py34
9 - python: 3.5
10 env: TOXENV=py35
118 - python: 3.6
129 env: TOXENV=py36
10 - python: 3.7
11 env: TOXENV=py37
12 - python: 3.8
13 env: TOXENV=py38
14 - python: 3.9
15 env: TOXENV=py39
1316 - python: pypy
1417 env: TOXENV=pypy
1518 - python: pypy3
1619 env: TOXENV=pypy3
17 - python: 3.6
20 - python: 3.8
1821 env: TOXENV=docs
1922 install:
2023 - pip install tox
+0
-104
CHANGELOG.md less more
0 # Flask-HTTPAuth Change Log
1
2 ## Release 3.2.2 - 2017-01-29
3
4 - Validate authorization header in multi auth ([#51](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/51))
5
6 ## Release 3.2.1 - 2016-09-04
7
8 - Added `__version__` to top-level package
9 - Added readme and license files to package
10
11 ## Release 3.2.0 - 2016-08-20
12
13 - Changed license to MIT
14 - Fix TCP Connection reset by peer error ([#39](https://github.com/miguelgrinberg/Flask-HTTPAuth/pull/39))
15
16 ## Release 3.1.2 - 2016-04-20
17
18 - Make password check more robust.
19
20 ## Release 3.1.1 - 2016-03-24
21
22 - `MultiAuth` class did not pass parameters to decorated function. ([#35](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/35))
23
24 ## Release 3.1.0 - 2016-03-13
25
26 - Added `MultiAuth` class, to allow the combination of multiple authentication methods.
27 - Added additional test for token authentication
28 - Added a few examples
29
30 ## Release 3.0.2 - 2016-03-11
31
32 - Invoke `verify_password` callback with no authentication when the provided authentication does not match the scheme
33
34 ## Release 3.0.1 - 2016-03-09
35
36 - Prevented crash when client sends an invalid authorization header for token auth
37
38 ## Release 3.0.0 - 2016-03-06
39
40 - Added token authentication support
41 - Switch Travis CI builds to use tox
42 - Refactored tests into separate test packages for each authentication method
43 - Added explicit Python 2 and 3 classifiers to setup script
44
45 ## Release 2.7.1 - 2016-02-07
46
47 - Correctly obtain nonce and opaque values in `authenticate_header` function
48 - Documentation updates
49
50 ## Release 2.7.0 - 2015-09-19
51
52 - Support custom authentication scheme and realm
53
54 ## Release 2.6.0 - 2015-08-22
55
56 - Added callbacks for custom digest auth nonce/opaque generation
57 - Documentation updates
58 - Travis CI builds
59
60 ## Release 2.5.0 - 2015-04-25
61
62 - In digest auth, support the client providing a pre-generated "ha1" instead of plain text password
63 - Add "ha1" generation helper function for digest auth
64 - Documentation updates
65
66 ## Release 2.4.0 - 2015-03-01
67
68 - Support anonymous users in `verify_password` callback
69 - Unit test fixes
70
71 ## Release 2.3.0 - 2014-09-23
72
73 - Corrections to `hash_password` and `verify_password` decorators
74 - Bypass authentication for `OPTIONS` requests
75 - Pep8 compliance
76
77 ## Release 2.2.1 - 2014-03-16
78
79 - Fixed documentation examples
80 - Corrections to `get_password` decorator implementation
81
82 ## Release 2.2.0 - 2013-11-25
83
84 - Build fixes
85
86 ## Release 2.1.0 - 2013-09-27
87
88 - Support optionally passing the username to the hash password callback
89
90 ## Release 2.0.0 - 2013-09-26
91
92 - Changed `auth.username` property to a `auth.username()` function
93 - Documentation updates
94
95 ## Release 1.1.0 - 2013-08-30
96
97 - Python 3 support
98 - Documentation updates
99
100 ## Release 1.0.0 - 2013-07-27
101
102 - First official release
103
0 # Flask-HTTPAuth change log
1
2 **Release 4.4.0** - 2021-05-13
3
4 - Replace `safe_str_cmp` with `hmac.compare_digest` to avoid a deprecation warning from Werkzeug [#126](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/126) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/79e3ebf77f4ad6a56a02996a08c4517f61151d49)) (thanks **Federico Martinez**!)
5 - Drop Python 2 support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/e690ce56827de9d669718fa5d0fcda63112f8008))
6
7 **Release 4.3.0** - 2021-05-01
8
9 - Support token auth with custom header in MultiAuth class [#125](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/125) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/6509081c72a2f92c1500b3f09aa063441ea60031))
10 - Catch `UnicodeDecodeError` when passing malformed data in authorization header [#122](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/122) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/538569f5895834a9f7b8d4dcfd543be6fbfca37e)) (thanks **Bastian Raschke**!)
11 - Fixes typo [#116](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/116) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9b4659e47b7e05a630f91b7e9471feef5111b503)) (thanks **Renato Oliveira**!)
12 - Move builds to GitHub actions ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/588b277cae820a680199e0acf5a97e2be50c6f6c))
13
14 **Release 4.2.0** - 2020-11-16
15
16 - Allow error response to return a 200 status code [#114](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/114) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f3e6a5754e89cda30fa88ef8b9dfa31e1697a688))
17 - Add optional argument to MultiAuth class [#115](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/115) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/e3c6e5fb0481c14c326460408c2d0d038adf7ddc)) (thanks **pryankster** and **Michael Wright**!)
18 - Remove python 3.5 and add python 3.9 to build ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/507a7c0bfdf7da3bfb6a0cff9624295cf1119986))
19
20 **Release 4.1.0** - 2020-06-04
21
22 - Basic authentication with custom scheme ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/1aaf872716cb46330fd49e89663da1a568e54f0b))
23
24 **Release 4.0.0** - 2020-04-26
25
26 - Return user object from verify callbacks ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/51748c24f5aa53175b0f2712b814f7ea581f04e4))
27 - New role authorization support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8178f6dd74dab47b993ba532dd12f0cfdb5799f1)) (thanks **gemerden**!)
28 - Add a custom token authorization header option ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/575b46ade7188152e1b82de84be949bf3f8a300b)) (thanks **Mohamed Feddad**!)
29 - Support an optional=True argument in `login_required` decorator ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8ecbb1157822360f5bdb24231fd50f25a6247620)) (thanks **Saif Almansoori**!)
30 - Pass HTTP status code to error callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fc8bcd6772b53ef5cc14cd4c6199d63cd2c71f30))
31 - More secure example of basic auth in the documentation ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0043e138cd99c7e9fa179ee30ad2283f9b8c704f))
32 - Fix broken links in CHANGES.md and changelog template [#85](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/85) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/96fafd43c2d0275f2d4042e95faefce24183ec02)) (thanks **Katie Smith**!)
33
34 **Release 3.3.0** - 2019-05-19
35
36 - Use constant time string comparisons [#82](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/82) ([commit1](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/788d42ea9c4d536af628e0e7f4cb1fb84fc59a8e), [commit2](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/97f0e641a6d5eb34054de1ca255e932313d441ee)) (thanks **Brendan Long**!)
37 - Edited and changed the usage of JWT, because in fact the code and documentation uses JWS tokens. [#79](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/79) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/3f743c661e281d728bd2f98af8cca000a975bb8a)) (thanks **unuseless**!)
38 - Documentation fix [#78](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/78) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c38c52326b78c91d4410f347abcd8bc49cc63ca4))
39 - Documentation improvements [#77](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/77) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ce5e5b4c9e8b748eba886ded5180e1e5d5036528))
40 - helper release script ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/7276d8db4b695645b01f3275addbec10418da63d))
41
42 **Release 3.2.4** - 2018-06-17
43
44 - Refactored HTTPAuth login_required [#74](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/74) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/68ee1e7a92355ba0f3f9b48c9489a67ab762e106)) (thanks **nestedsoftware**!)
45 - remove incorrect references to JWT in example application [#69](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/69) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/a310b78db2b947ab70f3fc35c1a586d822acc7ca))
46 - Fix typo in docs [#70](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/70) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/b6457ae5648a50df75f3c40af4b4b3f0155fc25f)) (thanks **Grey Li**!)
47 - Fix documentation [#67](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/67) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9bd8f4b4f3574c7ef3e2fb9596bc9e9981275011)) (thanks **Eugene Rymarev**!)
48 - correct spelling mistake [#56](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/56) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f7c5bbd1b3a53080171bbdc5f1f1842f7a825f6a)) (thanks **Edward Betts**!)
49 - travis build fix for py36 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/6e7f32984bda8b82200793c1b3ec44ff3df3ad2b))
50
51 **Release 3.2.3** - 2017-06-05
52
53 - Include docs and tests in pypi source tarball [#55](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/55) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/054810ee351148b14571ba0a89ec17a543c35078)) (thanks **Chandan Kumar**!)
54
55 **Release 3.2.2** - 2017-01-30
56
57 - Validate authorization header in multi auth [#51](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/51) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/7a895d676a1b6998f58b61a177286b62dc2872f5))
58 - index.rst: Add a missing variable in a code snippet [#49](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/49) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f7fe976bbdc699e8bafaed729dfdd74d2b27d7db)) (thanks **Baptiste Fontaine**!)
59
60 **Release 3.2.1** - 2016-09-04
61
62 - add `__version__` to package ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/d188450987f226568fe0cdee0b6d480b375af64a))
63 - Add readme and license files to the built package [#45](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/45) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/1c35bec606f147bb23725d6ff3b0411f06828492))
64
65 **Release 3.2.0** - 2016-08-20
66
67 - Fix TCP Connection reset by peer error [#39](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/39) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/94f6c6d5a4866a43ff4f269eb351dce6232791a2)) (thanks **Joe Kemp**!)
68
69 **Release 3.1.2** - 2016-04-21
70
71 - Add robustness to password check ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/051fd88ee36a21a13255b4ec69e172c9ae4ad46d))
72
73 **Release 3.1.1** - 2016-03-24
74
75 - pass params to view function in MultiAuth [#36](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/36) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/319974602e55529006b9a8a4fde04ef08e042e83)) (thanks **vovanz**!)
76 - add examples to flake8 build ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/61b1b71b3b29f2936ac6a2077883da1faeaad09f))
77 - Added multi auth tests ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c443e7ebcc227fd3690c2cf943d414087d7b931d))
78 - removed dead code ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4d2232e2a77f5e10e1731936f4ac64439049b220))
79
80 **Release 3.1.0** - 2016-03-13
81
82 - examples ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/609806a1c10264818e08ba0ce9b7babeaf101656))
83 - Added support for multiple authentication methods ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/6c3f94d9eda85b78a8c36cd5e05d6d9836bee2d0))
84 - Added change log ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8b427b962114a6ef13badaf8f2f1b396c540955a))
85 - Add additional token auth test ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/29edb1948f086babbd1a9e0c87a0a35c05f0a63b))
86
87 **Release 3.0.2** - 2016-03-12
88
89 - Let callback decide what to do when authentication type does not match ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/b942f980970d2e387a80f68de4ea2bb8728b149c))
90
91 **Release 3.0.1** - 2016-03-09
92
93 - Catching exception when Authorization header is empty ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/88d073e05b56b810feb447d1c9cee7a9a9ac9b1b)) (thanks **Kari Hreinsson**!)
94 - Documentation fix, validate_token() -> verify_token() ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f4b41d736311638978c95c9b5fd458063a009280)) (thanks **Kari Hreinsson**!)
95
96 **Release 3.0.0** - 2016-03-07
97
98 - documentation for new token auth ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c0ae42df517a45be87f419cbb7f8002228a1e83c))
99 - switch travis build to use tox ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/00fdebce667e1dbbc5b342a21804cb6ab3b4f417))
100 - token auth support, plus test reorg ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/aac866de14c68a4d17d3098f8e96102e837add1d))
101 - Added explicity Python 2 & 3 version classifiers to package ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/a6f50e7be6f13bb814c47fe8a3a44cd34138f87e))
102
103 **Release 2.7.1** - 2016-02-07
104
105 - Remove session dependency in authenticate_header [#31](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/31) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8a84c52d2166e7fdfa26b89dfd2df3340787de94)) (thanks **Paweł Stiasny**!)
106 - Add Install Notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0ff88331c9724999d8f283d79fe95de949e64438)) (thanks **Michael Washburn Jr**!)
107 - Add syntax highlighting to the README [#28](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/28) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5c058b5165cdbc6a869d68410ef2d25e7802d602)) (thanks **Josh Friend**!)
108
109 **Release 2.7.0** - 2015-09-20
110
111 - Support custom authentication scheme and realm ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/bf12f959bba24a2f3d7d799d1b57ef3a5f1001e8))
112
113 **Release 2.6.0** - 2015-08-23
114
115 - Added information on how to implement digest authentication securely ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fb02625ca0f7694d8e744e0b3d2c8d4ffcc4d7cd))
116 - Allow for custom nonce/opaque generation [#24](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/24) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ddaa3b6461705d107655c7f87f90d7ba962d2a84)) (thanks **Matt Haggard**!)
117 - fixed tests to work with python 2.6 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5e85b27a06285fb5bd591f9f65a8a0bebc4a34f2))
118 - added travis ci badge ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ef354fd07abd08137beba6362debdcb4ef23baf6))
119
120 **release 2.5.0** - 2015-04-26
121
122 - documentation changes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5c98ed8370355a60e22e017a79d5575adadb9c07))
123 - documentation for stored ha1 feature ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/37fd9288abb4f11abf9f93303d1bce4e6cfc3c19))
124 - Include notes for nginx ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ed8b4a3c954240cde0c66af3d6dae37df48ba976)) (thanks **Erik Stephens**!)
125 - Include notes for nginx as well ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5bccbae862cbf1ca7d02f717b076aca86b1456e5)) (thanks **Erik Stephens**!)
126 - Update docs with WSGI notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9ddd55f0bcb793a49675274dc22ae15122a8a1ff)) (thanks **Erik Stephens**!)
127 - Update README with WSGI notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/af5fa26dc73d401de7760ba3dcd61828c2e548dd)) (thanks **Erik Stephens**!)
128 - Modified documents and readme for correct import statement [#19](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/19) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/b75737593f3d97b18620440e7e41ee9b71b23f11)) (thanks **Aayush Kasurde**!)
129
130 **release 2.4.0** - 2015-03-02
131
132 - Support anonymous users in verify_password callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5c5396bbb7af540a7aff786ce3282657566045f2))
133 - Add HA1 generation function to HTTPDigestAuth class ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4f4aed3ed3fa5e96a1a052e4414f14d1fc49b8bb)) (thanks **Pawel Szczurko**!)
134 - Fix unit test url routes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/a490a521a17313ce82bfe886912b1620166eb6dd)) (thanks **Pawel Szczurko**!)
135 - Add option to use ha1 combination as password instead of plain text password ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c84429f541ed0069f40fb901dcb3df44b801c9a5)) (thanks **Pawel Szczurko**!)
136 - removed extra strip() calls in unit tests ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fc34cc5020168ca3824cc4a740b2010bb3132abf))
137
138 **release 2.3.0** - 2014-09-23
139
140 - pep8 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4657d5b37e50483ecccabf0887ea417d3b94ea0a))
141 - Fixed problem with couple of decorator that destroy function they decorate [#11](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/11) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0adf45bec7e5fb04a0e14e13396fd867879026b4)) (thanks **Nemanja Trifunovic**!)
142 - Ignore authentication headers for OPTIONS ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/044b7d4a44425a4b9d02280b80988e8986641a0d)) (thanks **Henrique Carvalho Alves**!)
143
144 **release 2.2.1** - 2014-03-17
145
146 - [#5](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/5): correct handling of None return from get_password callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/b94dc8e5fb6c914fdf971085b329bf9ad848a8f5))
147 - [#5](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/5) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/051195d68d8aaf6d9e53d14d69a59afd84f24821))
148 - Fixed problem when get_password decorator destroys function it decorates [#4](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/4) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0cbee173e96f8e1a533e7d82b5b1fa1bfce3cd04)) (thanks **Nemanja Trifunovic**!)
149 - custom password verification callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/33d60f21a6e64f1b2df24ea5035164110979d8ab))
150
151 **version 2.1.0** - 2013-09-28
152
153 - pass the username to the hash password callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/13075ec4dbe4cb733f4f433e1e25e8a180fce1f6))
154
155 **Release 2.0.0** - 2013-09-26
156
157 - changed auth.username to auth.username() ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5168a5f703552ec092e3fef9e087052e35fb6ff0))
158 - 2.0 documentation update ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/e668f59cb674e45891b7d9548e5af3028f2fd22d))
159
160 **Release 1.1.0** - 2013-08-30
161
162 - python 3 support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c13ff0a4c1e5922a635ea7c877a2ef6079ddb4e6))
163 - documentation update ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c468e1c084e5c25dcaa85b45e5abeb88fbc09420))
164
165 **Release 1.0.0** - 2013-07-27
166
167 - First official release!
00 Flask-HTTPAuth
11 ==============
22
3 [![Build Status](https://travis-ci.org/miguelgrinberg/Flask-HTTPAuth.png?branch=master)](https://travis-ci.org/miguelgrinberg/Flask-HTTPAuth)
3 [![Build status](https://github.com/miguelgrinberg/Flask-HTTPAuth/workflows/build/badge.svg)](https://github.com/miguelgrinberg/Flask-HTTPAuth/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/Flask-HTTPAuth/branch/master/graph/badge.svg?token=KeU2002DHo)](https://codecov.io/gh/miguelgrinberg/Flask-HTTPAuth)
44
55 Simple extension that provides Basic and Digest HTTP authentication for Flask routes.
66
1717 ```python
1818 from flask import Flask
1919 from flask_httpauth import HTTPBasicAuth
20 from werkzeug.security import generate_password_hash, check_password_hash
2021
2122 app = Flask(__name__)
2223 auth = HTTPBasicAuth()
2324
2425 users = {
25 "john": "hello",
26 "susan": "bye"
26 "john": generate_password_hash("hello"),
27 "susan": generate_password_hash("bye")
2728 }
2829
29 @auth.get_password
30 def get_pw(username):
31 if username in users:
32 return users.get(username)
33 return None
30 @auth.verify_password
31 def verify_password(username, password):
32 if username in users and \
33 check_password_hash(users.get(username), password):
34 return username
3435
3536 @app.route('/')
3637 @auth.login_required
3738 def index():
38 return "Hello, %s!" % auth.username()
39 return "Hello, %s!" % auth.current_user()
3940
4041 if __name__ == '__main__':
4142 app.run()
7980
8081 - [Documentation](http://flask-httpauth.readthedocs.io/en/latest/)
8182 - [PyPI](https://pypi.org/project/Flask-HTTPAuth)
82 - [Change log](https://github.com/miguelgrinberg/Flask-HTTPAuth/blob/master/CHANGELOG.md)
83 - [Change log](https://github.com/miguelgrinberg/Flask-HTTPAuth/blob/master/CHANGES.md)
0 import datetime
1 import re
2 import sys
3 import git
4
5 URL = 'https://github.com/miguelgrinberg/Flask-HTTPAuth'
6 merges = {}
7
8
9 def format_message(commit):
10 if commit.message.startswith('Version '):
11 return ''
12 if '#nolog' in commit.message:
13 return ''
14 if commit.message.startswith('Merge pull request'):
15 pr = commit.message.split('#')[1].split(' ')[0]
16 message = ' '.join([line for line in [line.strip() for line in commit.message.split('\n')[1:]] if line])
17 merges[message] = pr
18 return ''
19 if commit.message.startswith('Release '):
20 return '\n**{message}** - {date}\n'.format(
21 message=commit.message.strip(),
22 date=datetime.datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d'))
23 message = ' '.join([line for line in [line.strip() for line in commit.message.split('\n')] if line])
24 if message in merges:
25 message += ' #' + merges[message]
26 message = re.sub('\\(.*(#[0-9]+)\\)', '\\1', message)
27 message = re.sub('Fixes (#[0-9]+)', '\\1', message)
28 message = re.sub('fixes (#[0-9]+)', '\\1', message)
29 message = re.sub('#([0-9]+)', '[#\\1]({url}/issues/\\1)'.format(url=URL), message)
30 message += ' ([commit]({url}/commit/{sha}))'.format(url=URL, sha=str(commit))
31 if commit.author.name != 'Miguel Grinberg':
32 message += ' (thanks **{name}**!)'.format(name=commit.author.name)
33 return '- ' + message
34
35
36 def main(all=False):
37 repo = git.Repo()
38
39 for commit in repo.iter_commits():
40 if not all and commit.message.startswith('Release '):
41 break
42 message = format_message(commit)
43 if message:
44 print(message)
45
46
47 if __name__ == '__main__':
48 main(all=len(sys.argv) > 1 and sys.argv[1] == 'all')
0 #!/bin/bash -ex
1
2 VERSION="$1"
3 VERSION_FILE=flask_httpauth.py
4
5 if [[ "$VERSION" == "" ]]; then
6 echo "Usage: $0 <version>"
7 fi
8
9 # update change log
10 head -n 2 CHANGES.md > _CHANGES.md
11 echo "**Release $VERSION** - $(date +%F)" >> _CHANGES.md
12 echo "" >> _CHANGES.md
13 pip install gitpython
14 python bin/mkchangelog.py >> _CHANGES.md
15 echo "" >> _CHANGES.md
16 len=$(wc -l < CHANGES.md)
17 tail -n $(expr $len - 2) CHANGES.md >> _CHANGES.md
18 vim _CHANGES.md
19 set +e
20 grep -q ABORT _CHANGES.md
21 if [[ "$?" == "0" ]]; then
22 rm _CHANGES.md
23 echo "Aborted."
24 exit 1
25 fi
26 set -e
27 mv _CHANGES.md CHANGES.md
28
29 sed -i "" "s/^__version__ = '.*'$/__version__ = '$VERSION'/" $VERSION_FILE
30 rm -rf dist
31 pip install --upgrade pip wheel twine
32 python setup.py sdist bdist_wheel --universal
33
34 git add $VERSION_FILE CHANGES.md
35 git commit -m "Release $VERSION"
36 git tag -f v$VERSION
37 git push --tags origin master
38
39 read -p "Press any key to submit to PyPI or Ctrl-C to abort..." -n1 -s
40 twine upload dist/*
41
42 NEW_VERSION="${VERSION%.*}.$((${VERSION##*.}+1))dev"
43 sed -i "" "s/^__version__ = '.*'$/__version__ = '$NEW_VERSION'/" $VERSION_FILE
44 git add $VERSION_FILE
45 git commit -m "Version $NEW_VERSION"
46 git push origin master
47 echo "Development is now open on version $NEW_VERSION!"
55 Welcome to Flask-HTTPAuth's documentation!
66 ==========================================
77
8 **Flask-HTTPAuth** is a simple extension that simplifies the use of HTTP authentication with Flask routes.
9
10 Basic authentication example
11 ----------------------------
8 **Flask-HTTPAuth** is a Flask extension that simplifies the use of HTTP authentication with Flask routes.
9
10 Basic authentication examples
11 -----------------------------
1212
1313 The following example application uses HTTP Basic authentication to protect route ``'/'``::
1414
15 from flask import Flask
16 from flask_httpauth import HTTPBasicAuth
17
18 app = Flask(__name__)
19 auth = HTTPBasicAuth()
20
21 users = {
22 "john": "hello",
23 "susan": "bye"
24 }
25
26 @auth.get_password
27 def get_pw(username):
28 if username in users:
29 return users.get(username)
30 return None
31
32 @app.route('/')
33 @auth.login_required
34 def index():
35 return "Hello, %s!" % auth.username()
36
37 if __name__ == '__main__':
38 app.run()
39
40 The ``get_password`` callback needs to return the password associated with the username given as argument. Flask-HTTPAuth will allow access only if ``get_password(username) == password``.
41
42 If the passwords are stored hashed in the user database then an additional callback is needed::
43
44 @auth.hash_password
45 def hash_pw(password):
46 return md5(password).hexdigest()
47
48 When the ``hash_password`` callback is provided access will be granted when ``get_password(username) == hash_password(password)``.
49
50 If the hashing algorithm requires the username to be known then the callback can take two arguments instead of one::
51
52 @auth.hash_password
53 def hash_pw(username, password):
54 salt = get_salt(username)
55 return hash(password, salt)
56
57 For the most degree of flexibility the `get_password` and `hash_password` callbacks can be replaced with `verify_password`::
58
59 @auth.verify_password
60 def verify_pw(username, password):
61 return call_custom_verify_function(username, password)
62
63 In the examples directory you can find an example called `basic_auth.py` that shows how a `verify_password` callback can be used to securely work with hashed passwords.
15 from flask import Flask
16 from flask_httpauth import HTTPBasicAuth
17 from werkzeug.security import generate_password_hash, check_password_hash
18
19 app = Flask(__name__)
20 auth = HTTPBasicAuth()
21
22 users = {
23 "john": generate_password_hash("hello"),
24 "susan": generate_password_hash("bye")
25 }
26
27 @auth.verify_password
28 def verify_password(username, password):
29 if username in users and \
30 check_password_hash(users.get(username), password):
31 return username
32
33 @app.route('/')
34 @auth.login_required
35 def index():
36 return "Hello, {}!".format(auth.current_user())
37
38 if __name__ == '__main__':
39 app.run()
40
41 The function decorated with the ``verify_password`` decorator receives the username and password sent by the client. If the credentials belong to a user, then the function should return the user object. If the credentials are invalid the functon can return ``None`` or ``False``. The user object can then be queried from the ``current_user()`` method of the authentication instance.
6442
6543 Digest authentication example
6644 -----------------------------
6745
68 The following example is similar to the previous one, but HTTP Digest authentication is used::
46 The following example uses HTTP Digest authentication::
6947
7048 from flask import Flask
7149 from flask_httpauth import HTTPDigestAuth
8866 @app.route('/')
8967 @auth.login_required
9068 def index():
91 return "Hello, %s!" % auth.username()
69 return "Hello, {}!".format(auth.username())
9270
9371 if __name__ == '__main__':
9472 app.run()
124102
125103 For information of what the ``nonce`` and ``opaque`` values are and how they are used in digest authentication, consult `RFC 2617 <http://tools.ietf.org/html/rfc2617#section-3.2.1>`_.
126104
127 Token Authentication Scheme Example
128 -----------------------------------
105 Token Authentication Example
106 ----------------------------
129107
130108 The following example application uses a custom HTTP authentication scheme to protect route ``'/'`` with a token::
131109
133111 from flask_httpauth import HTTPTokenAuth
134112
135113 app = Flask(__name__)
136 auth = HTTPTokenAuth(scheme='Token')
114 auth = HTTPTokenAuth(scheme='Bearer')
137115
138116 tokens = {
139117 "secret-token-1": "john",
143121 @auth.verify_token
144122 def verify_token(token):
145123 if token in tokens:
146 g.current_user = tokens[token]
147 return True
148 return False
124 return tokens[token]
149125
150126 @app.route('/')
151127 @auth.login_required
152128 def index():
153 return "Hello, %s!" % g.current_user
129 return "Hello, {}!".format(auth.current_user())
154130
155131 if __name__ == '__main__':
156132 app.run()
157133
158 The ``HTTPTokenAuth`` is a generic authentication handler that can be used with non-standard authentication schemes, with the scheme name given as an argument in the constructor. In the above example, the ``WWW-Authenticate`` header provided by the server will use ``Token`` as scheme::
159
160 WWW-Authenticate: Token realm="Authentication Required"
161
162 The ``verify_token`` callback receives the authentication credentials provided by the client on the ``Authorization`` header. This can be a simple token, or can contain multiple arguments, which the function will have to parse and extract from the string.
163
164 In the examples directory you can find a complete example that uses JWT tokens.
134 The ``HTTPTokenAuth`` is a generic authentication handler that can be used with non-standard authentication schemes, with the scheme name given as an argument in the constructor. In the above example, the ``WWW-Authenticate`` header provided by the server will use ``Bearer`` as scheme::
135
136 WWW-Authenticate: Bearer realm="Authentication Required"
137
138 The ``verify_token`` callback receives the authentication credentials provided by the client on the ``Authorization`` header. This can be a simple token, or can contain multiple arguments, which the function will have to parse and extract from the string. As with the ``verify_password``, the function should return the user object if the token is valid.
139
140 In the examples directory you can find a complete example that uses JWS tokens. JWS tokens are similar to JWT tokens. However using JWT tokens would require an external dependency.
165141
166142 Using Multiple Authentication Schemes
167143 -------------------------------------
168144
169 Applications sometimes need to support a combination of authentication methods. For example, a web application could be authenticated by sending client id and secret over basic authentication, while third party API clients use a JWT bearer token. The `MultiAuth` class allows you to protect a route with more than one authentication object. To grant access to the endpoint, one of the authentication methods must validate.
145 Applications sometimes need to support a combination of authentication
146 methods. For example, a web application could be authenticated by
147 sending client id and secret over basic authentication, while third
148 party API clients use a JWS or JWT bearer token. The `MultiAuth` class allows you to protect a route with more than one authentication object. To grant access to the endpoint, one of the authentication methods must validate.
170149
171150 In the examples directory you can find a complete example that uses basic and token authentication.
151
152 User Roles
153 ----------
154
155 Flask-HTTPAuth includes a simple role-based authentication system that can optionally be added to provide an additional layer of granularity in filtering accesses to routes. To enable role support, write a function that returns the list of roles for a given user and decorate it with the ``get_user_roles`` decorator::
156
157 @auth.get_user_roles
158 def get_user_roles(user):
159 return user.get_roles()
160
161 To restrict access to a route to users having a given role, add the ``role`` argument to the ``login_required`` decorator::
162
163 @app.route('/admin')
164 @auth.login_required(role='admin')
165 def admins_only():
166 return "Hello {}, you are an admin!".format(auth.current_user())
167
168 The ``role`` argument can take a list of roles, in which case users who have any of the given roles will be granted access::
169
170 @app.route('/admin')
171 @auth.login_required(role=['admin', 'moderator'])
172 def admins_only():
173 return "Hello {}, you are an admin or a moderator!".format(auth.current_user())
174
175 In the most advanced usage, users can be filtered by having multiple roles::
176
177 @app.route('/admin')
178 @auth.login_required(role=['user', ['moderator', 'contributor']])
179 def admins_only():
180 return "Hello {}, you are a user or a moderator/contributor!".format(auth.current_user())
172181
173182 Deployment Considerations
174183 -------------------------
175184
176185 Be aware that some web servers do not pass the ``Authorization`` headers to the WSGI application by default. For example, if you use Apache with mod_wsgi, you have to set option ``WSGIPassAuthorization On`` as `documented here <https://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization/>`_.
177186
187 Deprecated Basic Authentication Options
188 ---------------------------------------
189
190 Before the ``verify_password`` described above existed there were other simpler mechanisms for implementing basic authentication. While these are deprecated they are still maintained. However, the ``verify_password`` callback should be preferred as it provides greater security and flexibility.
191
192 The ``get_password`` callback needs to return the password associated with the username given as argument. Flask-HTTPAuth will allow access only if ``get_password(username) == password``. Example::
193
194 @auth.get_password
195 def get_password(username):
196 return get_password_for_username(username)
197
198 Using this callback alone is in general not a good idea because it requires passwords to be available in plaintext in the server. In the more likely scenario that the passwords are stored hashed in a user database, then an additional callback is needed to define how to hash a password::
199
200 @auth.hash_password
201 def hash_pw(password):
202 return hash_password(password)
203
204 In this example, you have to replace ``hash_password()`` with the specific hashing function used in your application. When the ``hash_password`` callback is provided, access will be granted when ``get_password(username) == hash_password(password)``.
205
206 If the hashing algorithm requires the username to be known then the callback can take two arguments instead of one::
207
208 @auth.hash_password
209 def hash_pw(username, password):
210 salt = get_salt(username)
211 return hash_password(password, salt)
212
178213 API Documentation
179214 -----------------
180215
192227
193228 The ``realm`` argument can be used to provide an application defined realm with the ``WWW-Authenticate`` header.
194229
230 .. method:: verify_password(verify_password_callback)
231
232 If defined, this callback function will be called by the framework to verify that the username and password combination provided by the client are valid. The callback function takes two arguments, the username and the password. It must return the user object if credentials are valid, or ``True`` if a user object is not available. In case of failed authentication, it should return ``None`` or ``False``. Example usage::
233
234 @auth.verify_password
235 def verify_password(username, password):
236 user = User.query.filter_by(username).first()
237 if user and passlib.hash.sha256_crypt.verify(password, user.password_hash):
238 return user
239
240 If this callback is defined, it is also invoked when the request does not have the ``Authorization`` header with user credentials, and in this case both the ``username`` and ``password`` arguments are set to empty strings. The application can opt to return ``True`` in this case and that will allow anonymous users access to the route. The callback function can indicate that the user is anonymous by writing a state variable to ``flask.g`` or by checking if ``auth.current_user()`` is ``None``.
241
242 Note that when a ``verify_password`` callback is provided the ``get_password`` and ``hash_password`` callbacks are not used.
243
244 .. method:: get_user_roles(roles_callback)
245
246 If defined, this callback function will be called by the framework to obtain the roles assigned to a given user. The callback function takes a single argument, the user for which roles are requested. The user object passed to this function will be the one returned by the ``verify_callback`` function. The function should return the role or list of roles that belong to the user. Example::
247
248 @auth.get_user_roles
249 def get_user_roles(user):
250 return user.get_roles()
251
195252 .. method:: get_password(password_callback)
196253
197 This callback function will be called by the framework to obtain the password for a given user. Example::
254 *Deprecated* This callback function will be called by the framework to obtain the password for a given user. Example::
198255
199256 @auth.get_password
200257 def get_password(username):
202259
203260 .. method:: hash_password(hash_password_callback)
204261
205 If defined, this callback function will be called by the framework to apply a custom hashing algorithm to the password provided by the client. If this callback isn't provided the password will be checked unchanged. The callback can take one or two arguments. The one argument version receives the password to hash, while the two argument version receives the username and the password in that order. Example single argument callback::
262 *Deprecated* If defined, this callback function will be called by the framework to apply a custom hashing algorithm to the password provided by the client. If this callback isn't provided the password will be checked unchanged. The callback can take one or two arguments. The one argument version receives the password to hash, while the two argument version receives the username and the password in that order. Example single argument callback::
206263
207264 @auth.hash_password
208265 def hash_password(password):
215272 salt = get_salt(username)
216273 return hash(password, salt)
217274
218 .. method:: verify_password(verify_password_callback)
219
220 If defined, this callback function will be called by the framework to verify that the username and password combination provided by the client are valid. The callback function takes two arguments, the username and the password and must return ``True`` or ``False``. Example usage::
221
222 @auth.verify_password
223 def verify_password(username, password):
224 user = User.query.filter_by(username).first()
225 if not user:
226 return False
227 return passlib.hash.sha256_crypt.verify(password, user.password_hash)
228
229 If this callback is defined, it is also invoked when the request does not have the ``Authorization`` header with user credentials, and in this case both the ``username`` and ``password`` arguments are set to empty strings. The client can opt to return ``True`` and that will allow anonymous users access to the route. The callback function can indicate that the user is anonymous by writing a state variable to ``flask.g``, which the route can then check to generate an appropriate response.
230
231 Note that when a ``verify_password`` callback is provided the ``get_password`` and ``hash_password`` callbacks are not used.
232
233275 .. method:: error_handler(error_callback)
234276
235 If defined, this callback function will be called by the framework when it is necessary to send an authentication error back to the client. The return value from this function can be the body of the response as a string or it can also be a response object created with ``make_response``. If this callback isn't provided a default error response is generated. Example::
277 If defined, this callback function will be called by the framework when it is necessary to send an authentication error back to the client. The function can take one argument, the status code of the error, which can be 401 (incorrect credentials) or 403 (correct, but insufficient credentials). To preserve compatiiblity with older releases of this package, the function can also be defined without arguments. The return value from this function must by any accepted response type in Flask routes. If this callback isn't provided a default error response is generated. Example::
236278
237279 @auth.error_handler
238 def auth_error():
239 return "&lt;h1&gt;Access Denied&lt;/h1&gt;"
280 def auth_error(status):
281 return "Access Denied", status
240282
241283 .. method:: login_required(view_function_callback)
242284
247289 def private_page():
248290 return "Only for authorized people!"
249291
250 .. method:: username()
251
252 A view function that is protected with this class can access the logged username through this method. Example::
292 An optional ``role`` argument can be given to further restrict access by roles. Example::
293
294 @app.route('/private')
295 @auth.login_required(role='admin')
296 def private_page():
297 return "Only for admins!"
298
299 An optional ``optional`` argument can be set to ``True`` to allow the route to execute also when authentication is not included with the request, in which case ``auth.current_user()`` will be set to ``None``. Example::
300
301 @app.route('/private')
302 @auth.login_required(optional=True)
303 def private_page():
304 user = auth.current_user()
305 return "Hello {}!".format(user.name if user is not None else 'anonymous')
306
307 .. method:: current_user()
308
309 The user object returned by the ``verify_password`` callback on successful authentication. If no user is returned by the callback, this is set to the username passed by the client. Example::
253310
254311 @app.route('/')
255312 @auth.login_required
256313 def index():
257 return "Hello, %s!" % auth.username()
258
259 .. class:: flask_httpauth.HTTPDigestAuth
260
261 This class handles HTTP Digest authentication for Flask routes. The ``SECRET_KEY`` configuration must be set in the Flask application to enable the session to work. Flask by default stores user sessions in the client as secure cookies, so the client must be able to handle cookies. To support clients that are not web browsers or that cannot handle cookies a `session interface <http://flask.pocoo.org/docs/api/#flask.Flask.session_interface>`_ that writes sessions in the server must be used.
314 user = auth.current_user()
315 return "Hello, {}!".format(user.name)
316
317 .. method:: username()
318
319 *Deprecated* A view function that is protected with this class can access the logged username through this method. Example::
320
321 @app.route('/')
322 @auth.login_required
323 def index():
324 return "Hello, {}!".format(auth.username())
325
326 .. class:: HTTPDigestAuth
327
328 This class handles HTTP Digest authentication for Flask routes. The ``SECRET_KEY`` configuration must be set in the Flask application to enable the session to work. Flask by default stores user sessions in the client as secure cookies, so the client must be able to handle cookies. To make this authentication method secure, a `session interface <http://flask.pocoo.org/docs/api/#flask.Flask.session_interface>`_ that writes sessions in the server must be used.
262329
263330 .. method:: __init__(self, scheme=None, realm=None, use_ha1_pw=False)
264331
309376 .. method:: get_password(password_callback)
310377
311378 See basic authentication for documentation and examples.
312
379
380 .. method:: get_user_roles(roles_callback)
381
382 See basic authentication for documentation and examples.
383
313384 .. method:: error_handler(error_callback)
314385
315386 See basic authentication for documentation and examples.
318389
319390 See basic authentication for documentation and examples.
320391
392 .. method:: current_user()
393
394 See basic authentication for documentation and examples.
395
321396 .. method:: username()
322397
323398 See basic authentication for documentation and examples.
326401
327402 This class handles HTTP authentication with custom schemes for Flask routes.
328403
329 .. method:: __init__(scheme, realm=None)
404 .. method:: __init__(scheme='Bearer', realm=None, header=None)
330405
331406 Create a token authentication object.
332407
333 The ``scheme`` argument must be provided to be used in the ``WWW-Authenticate`` response.
408 The ``scheme`` argument can be use to specify the scheme to be used in the ``WWW-Authenticate`` response. The ``Authorization`` header sent by the client must include this scheme followed by the token. Example::
409
410 Authorization: Bearer this-is-my-token
334411
335412 The ``realm`` argument can be used to provide an application defined realm with the ``WWW-Authenticate`` header.
336413
414 The ``header`` argument can be used to specify a custom header instead of ``Authorization`` from where to obtain the token. If a custom header is used, the ``scheme`` should not be included. Example::
415
416 X-API-Key: this-is-my-token
417
337418 .. method:: verify_token(verify_token_callback)
338419
339 This callback function will be called by the framework to verify that the credentials sent by the client with the ``Authorization`` header are valid. The callback function takes one argument, the username and the password and must return ``True`` or ``False``. Example usage::
420 This callback function will be called by the framework to verify that the credentials sent by the client with the ``Authorization`` header are valid. The callback function takes one argument, the token provided by the client. The function must return the user object if the token is valid, or ``True`` if a user object is not available. In case of a failed authentication, the function should return ``None`` or ``False``. Example usage::
340421
341422 @auth.verify_token
342423 def verify_token(token):
343 g.current_user = User.query.filter_by(token=token).first()
344 return g.current_user is not None
424 return User.query.filter_by(token=token).first()
345425
346426 Note that a ``verify_token`` callback is required when using this class.
347427
428 .. method:: get_user_roles(roles_callback)
429
430 See basic authentication for documentation and examples.
431
348432 .. method:: error_handler(error_callback)
349433
350434 See basic authentication for documentation and examples.
352436 .. method:: login_required(view_function_callback)
353437
354438 See basic authentication for documentation and examples.
439
440 .. method:: current_user()
441
442 See basic authentication for documentation and examples.
443
444 .. class:: HTTPMultiAuth
445
446 This class handles HTTP authentication with custom schemes for Flask routes.
447
448 .. method:: __init__(auth_object, ...)
449
450 Create a multiple authentication object.
451
452 The arguments are one or more instances of ``HTTPBasicAuth``, ``HTTPDigestAuth`` or ``HTTPTokenAuth``. A route protected with this authentication method will try all the given authentication objects until one succeeds.
453
454 .. method:: login_required(view_function_callback)
455
456 See basic authentication for documentation and examples.
457
458 .. method:: current_user()
459
460 See basic authentication for documentation and examples.
2222
2323 @auth.verify_password
2424 def verify_password(username, password):
25 if username in users:
26 return check_password_hash(users.get(username), password)
27 return False
25 if username in users and check_password_hash(users.get(username),
26 password):
27 return username
2828
2929
3030 @app.route('/')
3131 @auth.login_required
3232 def index():
33 return "Hello, %s!" % auth.username()
33 return "Hello, %s!" % auth.current_user()
3434
3535
3636 if __name__ == '__main__':
44 "MultiAuth" class.
55
66 The root URL for this application can be accessed via basic auth, providing
7 username and password, or via token auth, providing a bearer JWT token.
7 username and password, or via token auth, providing a bearer JWS token.
88 """
9 from flask import Flask, g
9 from flask import Flask
1010 from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth
1111 from werkzeug.security import generate_password_hash, check_password_hash
12 from itsdangerous import TimedJSONWebSignatureSerializer as JWT
12 from itsdangerous import TimedJSONWebSignatureSerializer as JWS
1313
1414
1515 app = Flask(__name__)
1616 app.config['SECRET_KEY'] = 'top secret!'
17 jwt = JWT(app.config['SECRET_KEY'], expires_in=3600)
17 jws = JWS(app.config['SECRET_KEY'], expires_in=3600)
1818
1919 basic_auth = HTTPBasicAuth()
2020 token_auth = HTTPTokenAuth('Bearer')
2727 }
2828
2929 for user in users.keys():
30 token = jwt.dumps({'username': user})
30 token = jws.dumps({'username': user})
3131 print('*** token for {}: {}\n'.format(user, token))
3232
3333
3434 @basic_auth.verify_password
3535 def verify_password(username, password):
36 g.user = None
3736 if username in users:
3837 if check_password_hash(users.get(username), password):
39 g.user = username
40 return True
41 return False
38 return username
4239
4340
4441 @token_auth.verify_token
4542 def verify_token(token):
46 g.user = None
4743 try:
48 data = jwt.loads(token)
44 data = jws.loads(token)
4945 except: # noqa: E722
5046 return False
5147 if 'username' in data:
52 g.user = data['username']
53 return True
54 return False
48 return data['username']
5549
5650
5751 @app.route('/')
5852 @multi_auth.login_required
5953 def index():
60 return "Hello, %s!" % g.user
54 return "Hello, %s!" % multi_auth.current_user()
6155
6256
6357 if __name__ == '__main__':
0 #!/usr/bin/env python
1 """Basic authentication example
2
3 This example demonstrates how to protect Flask endpoints with basic
4 authentication, using secure hashed passwords.
5
6 After running this example, visit http://localhost:5000 in your browser. To
7 gain access, you can use (username=john, password=hello) or
8 (username=susan, password=bye).
9 """
10 from flask import Flask
11 from flask_httpauth import HTTPBasicAuth
12 from werkzeug.security import generate_password_hash, check_password_hash
13
14 app = Flask(__name__)
15 auth = HTTPBasicAuth()
16
17 users = {
18 "john": generate_password_hash("hello"),
19 "susan": generate_password_hash("bye"),
20 }
21
22 roles = {
23 "john": "user",
24 "susan": ["user", "admin"],
25 }
26
27
28 @auth.get_user_roles
29 def get_user_roles(username):
30 return roles.get(username)
31
32
33 @auth.verify_password
34 def verify_password(username, password):
35 if username in users and check_password_hash(
36 users.get(username), password):
37 return username
38
39
40 @app.route('/')
41 @auth.login_required(role='user')
42 def index():
43 return "Hello, {}!".format(auth.current_user())
44
45
46 @app.route('/admin')
47 @auth.login_required(role='admin')
48 def admin():
49 return "Hello {}, you are an admin!".format(auth.current_user())
50
51
52 if __name__ == '__main__':
53 app.run(debug=True, host='0.0.0.0')
77 To gain access, you can use a command line HTTP client such as curl, passing
88 one of the tokens:
99
10 curl -X GET -H "Authorization: Bearer <jwt-token>" http://localhost:5000/
10 curl -X GET -H "Authorization: Bearer <jws-token>" http://localhost:5000/
1111
1212 The response should include the username, which is obtained from the token.
1313 """
14 from flask import Flask, g
14 from flask import Flask
1515 from flask_httpauth import HTTPTokenAuth
1616 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
1717
3131
3232 @auth.verify_token
3333 def verify_token(token):
34 g.user = None
3534 try:
3635 data = token_serializer.loads(token)
3736 except: # noqa: E722
3837 return False
3938 if 'username' in data:
40 g.user = data['username']
41 return True
42 return False
39 return data['username']
4340
4441
4542 @app.route('/')
4643 @auth.login_required
4744 def index():
48 return "Hello, %s!" % g.user
45 return "Hello, %s!" % auth.current_user()
4946
5047
5148 if __name__ == '__main__':
66 :copyright: (C) 2014 by Miguel Grinberg.
77 :license: MIT, see LICENSE for more details.
88 """
9
9 import hmac
10 from base64 import b64decode
1011 from functools import wraps
1112 from hashlib import md5
1213 from random import Random, SystemRandom
13 from flask import request, make_response, session
14 from flask import request, make_response, session, g, Response
1415 from werkzeug.datastructures import Authorization
1516
16 __version__ = '3.2.4'
17
18 __version__ = '4.4.0'
1719
1820
1921 class HTTPAuth(object):
20 def __init__(self, scheme=None, realm=None):
22 def __init__(self, scheme=None, realm=None, header=None):
2123 self.scheme = scheme
2224 self.realm = realm or "Authentication Required"
25 self.header = header
2326 self.get_password_callback = None
27 self.get_user_roles_callback = None
2428 self.auth_error_callback = None
2529
2630 def default_get_password(username):
2731 return None
2832
29 def default_auth_error():
30 return "Unauthorized Access"
33 def default_auth_error(status):
34 return "Unauthorized Access", status
3135
3236 self.get_password(default_get_password)
3337 self.error_handler(default_auth_error)
3438
39 def is_compatible_auth(self, headers):
40 if self.header is None or self.header == 'Authorization':
41 try:
42 scheme, _ = request.headers.get('Authorization', '').split(
43 None, 1)
44 except ValueError:
45 # malformed Authorization header
46 return False
47 return scheme == self.scheme
48 else:
49 return self.header in headers
50
3551 def get_password(self, f):
3652 self.get_password_callback = f
53 return f
54
55 def get_user_roles(self, f):
56 self.get_user_roles_callback = f
3757 return f
3858
3959 def error_handler(self, f):
4060 @wraps(f)
4161 def decorated(*args, **kwargs):
4262 res = f(*args, **kwargs)
63 check_status_code = not isinstance(res, (tuple, Response))
4364 res = make_response(res)
44 if res.status_code == 200:
65 if check_status_code and res.status_code == 200:
4566 # if user didn't set status code, use 401
4667 res.status_code = 401
4768 if 'WWW-Authenticate' not in res.headers.keys():
5475 return '{0} realm="{1}"'.format(self.scheme, self.realm)
5576
5677 def get_auth(self):
57 auth = request.authorization
58 if auth is None and 'Authorization' in request.headers:
59 # Flask/Werkzeug do not recognize any authentication types
60 # other than Basic or Digest, so here we parse the header by
61 # hand
62 try:
63 auth_type, token = request.headers['Authorization'].split(
64 None, 1)
65 auth = Authorization(auth_type, {'token': token})
66 except ValueError:
67 # The Authorization header is either empty or has no token
68 pass
78 auth = None
79 if self.header is None or self.header == 'Authorization':
80 auth = request.authorization
81 if auth is None and 'Authorization' in request.headers:
82 # Flask/Werkzeug do not recognize any authentication types
83 # other than Basic or Digest, so here we parse the header by
84 # hand
85 try:
86 auth_type, token = request.headers['Authorization'].split(
87 None, 1)
88 auth = Authorization(auth_type, {'token': token})
89 except (ValueError, KeyError):
90 # The Authorization header is either empty or has no token
91 pass
92 elif self.header in request.headers:
93 # using a custom header, so the entire value of the header is
94 # assumed to be a token
95 auth = Authorization(self.scheme,
96 {'token': request.headers[self.header]})
6997
7098 # if the auth type does not match, we act as if there is no auth
7199 # this is better than failing directly, as it allows the callback
83111
84112 return password
85113
86 def login_required(self, f):
87 @wraps(f)
88 def decorated(*args, **kwargs):
89 auth = self.get_auth()
90
91 # Flask normally handles OPTIONS requests on its own, but in the
92 # case it is configured to forward those to the application, we
93 # need to ignore authentication headers and let the request through
94 # to avoid unwanted interactions with CORS.
95 if request.method != 'OPTIONS': # pragma: no cover
96 password = self.get_auth_password(auth)
97
98 if not self.authenticate(auth, password):
99 # Clear TCP receive buffer of any pending data
100 request.data
101 return self.auth_error_callback()
102
103 return f(*args, **kwargs)
104 return decorated
114 def authorize(self, role, user, auth):
115 if role is None:
116 return True
117 if isinstance(role, (list, tuple)):
118 roles = role
119 else:
120 roles = [role]
121 if user is True:
122 user = auth
123 if self.get_user_roles_callback is None: # pragma: no cover
124 raise ValueError('get_user_roles callback is not defined')
125 user_roles = self.get_user_roles_callback(user)
126 if user_roles is None:
127 user_roles = {}
128 elif not isinstance(user_roles, (list, tuple)):
129 user_roles = {user_roles}
130 else:
131 user_roles = set(user_roles)
132 for role in roles:
133 if isinstance(role, (list, tuple)):
134 role = set(role)
135 if role & user_roles == role:
136 return True
137 elif role in user_roles:
138 return True
139
140 def login_required(self, f=None, role=None, optional=None):
141 if f is not None and \
142 (role is not None or optional is not None): # pragma: no cover
143 raise ValueError(
144 'role and optional are the only supported arguments')
145
146 def login_required_internal(f):
147 @wraps(f)
148 def decorated(*args, **kwargs):
149 auth = self.get_auth()
150
151 # Flask normally handles OPTIONS requests on its own, but in
152 # the case it is configured to forward those to the
153 # application, we need to ignore authentication headers and
154 # let the request through to avoid unwanted interactions with
155 # CORS.
156 if request.method != 'OPTIONS': # pragma: no cover
157 password = self.get_auth_password(auth)
158
159 status = None
160 user = self.authenticate(auth, password)
161 if user in (False, None):
162 status = 401
163 elif not self.authorize(role, user, auth):
164 status = 403
165 if not optional and status:
166 # Clear TCP receive buffer of any pending data
167 request.data
168 try:
169 return self.auth_error_callback(status)
170 except TypeError:
171 return self.auth_error_callback()
172
173 g.flask_httpauth_user = user if user is not True \
174 else auth.username if auth else None
175 return f(*args, **kwargs)
176 return decorated
177
178 if f:
179 return login_required_internal(f)
180 return login_required_internal
105181
106182 def username(self):
107 if not request.authorization:
183 auth = self.get_auth()
184 if not auth:
108185 return ""
109 return request.authorization.username
186 return auth.username
187
188 def current_user(self):
189 if hasattr(g, 'flask_httpauth_user'):
190 return g.flask_httpauth_user
110191
111192
112193 class HTTPBasicAuth(HTTPAuth):
123204 def verify_password(self, f):
124205 self.verify_password_callback = f
125206 return f
207
208 def get_auth(self):
209 # this version of the Authorization header parser is more flexible
210 # than Werkzeug's, as it also accepts other schemes besides "Basic"
211 header = self.header or 'Authorization'
212 if header not in request.headers:
213 return None
214 value = request.headers[header].encode('utf-8')
215 try:
216 scheme, credentials = value.split(b' ', 1)
217 username, password = b64decode(credentials).split(b':', 1)
218 except (ValueError, TypeError):
219 return None
220 try:
221 username = username.decode('utf-8')
222 password = password.decode('utf-8')
223 except UnicodeDecodeError:
224 username = None
225 password = None
226 return Authorization(
227 scheme, {'username': username, 'password': password})
126228
127229 def authenticate(self, auth, stored_password):
128230 if auth:
134236 if self.verify_password_callback:
135237 return self.verify_password_callback(username, client_password)
136238 if not auth:
137 return False
239 return
138240 if self.hash_password_callback:
139241 try:
140242 client_password = self.hash_password_callback(client_password)
141243 except TypeError:
142244 client_password = self.hash_password_callback(username,
143245 client_password)
144 return client_password is not None and \
145 client_password == stored_password
246 return auth.username if client_password is not None and \
247 stored_password is not None and \
248 hmac.compare_digest(client_password, stored_password) else None
146249
147250
148251 class HTTPDigestAuth(HTTPAuth):
168271 return session["auth_nonce"]
169272
170273 def default_verify_nonce(nonce):
171 return nonce == session.get("auth_nonce")
274 session_nonce = session.get("auth_nonce")
275 if nonce is None or session_nonce is None:
276 return False
277 return hmac.compare_digest(nonce, session_nonce)
172278
173279 def default_generate_opaque():
174280 session["auth_opaque"] = _generate_random()
175281 return session["auth_opaque"]
176282
177283 def default_verify_opaque(opaque):
178 return opaque == session.get("auth_opaque")
284 session_opaque = session.get("auth_opaque")
285 if opaque is None or session_opaque is None: # pragma: no cover
286 return False
287 return hmac.compare_digest(opaque, session_opaque)
179288
180289 self.generate_nonce(default_generate_nonce)
181290 self.generate_opaque(default_generate_opaque)
234343 ha2 = md5(a2.encode('utf-8')).hexdigest()
235344 a3 = ha1 + ":" + auth.nonce + ":" + ha2
236345 response = md5(a3.encode('utf-8')).hexdigest()
237 return response == auth.response
346 return hmac.compare_digest(response, auth.response)
238347
239348
240349 class HTTPTokenAuth(HTTPAuth):
241 def __init__(self, scheme='Bearer', realm=None):
242 super(HTTPTokenAuth, self).__init__(scheme, realm)
350 def __init__(self, scheme='Bearer', realm=None, header=None):
351 super(HTTPTokenAuth, self).__init__(scheme, realm, header)
243352
244353 self.verify_token_callback = None
245354
254363 token = ""
255364 if self.verify_token_callback:
256365 return self.verify_token_callback(token)
257 return False
258366
259367
260368 class MultiAuth(object):
262370 self.main_auth = main_auth
263371 self.additional_auth = args
264372
265 def login_required(self, f):
266 @wraps(f)
267 def decorated(*args, **kwargs):
268 selected_auth = None
269 if 'Authorization' in request.headers:
270 try:
271 scheme, creds = request.headers['Authorization'].split(
272 None, 1)
273 except ValueError:
274 # malformed Authorization header
275 pass
276 else:
373 def login_required(self, f=None, role=None, optional=None):
374 if f is not None and \
375 (role is not None or optional is not None): # pragma: no cover
376 raise ValueError(
377 'role and optional are the only supported arguments')
378
379 def login_required_internal(f):
380 @wraps(f)
381 def decorated(*args, **kwargs):
382 selected_auth = self.main_auth
383 if not self.main_auth.is_compatible_auth(request.headers):
277384 for auth in self.additional_auth:
278 if auth.scheme == scheme:
385 if auth.is_compatible_auth(request.headers):
279386 selected_auth = auth
280387 break
281 if selected_auth is None:
282 selected_auth = self.main_auth
283 return selected_auth.login_required(f)(*args, **kwargs)
284 return decorated
388 return selected_auth.login_required(role=role,
389 optional=optional
390 )(f)(*args, **kwargs)
391 return decorated
392
393 if f:
394 return login_required_internal(f)
395 return login_required_internal
396
397 def current_user(self):
398 if hasattr(g, 'flask_httpauth_user'): # pragma: no cover
399 return g.flask_httpauth_user
3333 'License :: OSI Approved :: MIT License',
3434 'Operating System :: OS Independent',
3535 'Programming Language :: Python',
36 'Programming Language :: Python :: 2',
3736 'Programming Language :: Python :: 3',
3837 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
3938 'Topic :: Software Development :: Libraries :: Python Modules'
44
55
66 class HTTPAuthTestCase(unittest.TestCase):
7 use_old_style_callback = False
8
79 def setUp(self):
810 app = Flask(__name__)
911 app.config['SECRET_KEY'] = 'my secret'
1214
1315 @basic_verify_auth.verify_password
1416 def basic_verify_auth_verify_password(username, password):
15 g.anon = False
16 if username == 'john':
17 return password == 'hello'
18 elif username == 'susan':
19 return password == 'bye'
20 elif username == '':
21 g.anon = True
22 return True
23 return False
17 if self.use_old_style_callback:
18 g.anon = False
19 if username == 'john':
20 return password == 'hello'
21 elif username == 'susan':
22 return password == 'bye'
23 elif username == '':
24 g.anon = True
25 return True
26 return False
27 else:
28 g.anon = False
29 if username == 'john' and password == 'hello':
30 return 'john'
31 elif username == 'susan' and password == 'bye':
32 return 'susan'
33 elif username == '':
34 g.anon = True
35 return ''
2436
2537 @basic_verify_auth.error_handler
2638 def error_handler():
39 self.assertIsNone(basic_verify_auth.current_user())
2740 return 'error', 403 # use a custom error status
2841
2942 @app.route('/')
3346 @app.route('/basic-verify')
3447 @basic_verify_auth.login_required
3548 def basic_verify_auth_route():
36 return 'basic_verify_auth:' + basic_verify_auth.username() + \
37 ' anon:' + str(g.anon)
49 if self.use_old_style_callback:
50 return 'basic_verify_auth:' + basic_verify_auth.username() + \
51 ' anon:' + str(g.anon)
52 else:
53 return 'basic_verify_auth:' + \
54 basic_verify_auth.current_user() + ' anon:' + str(g.anon)
3855
3956 self.app = app
4057 self.basic_verify_auth = basic_verify_auth
5673 '/basic-verify', headers={'Authorization': 'Basic ' + creds})
5774 self.assertEqual(response.status_code, 403)
5875 self.assertTrue('WWW-Authenticate' in response.headers)
76
77 def test_verify_auth_login_malformed_password(self):
78 creds = 'eyJhbGciOieyJp=='
79 response = self.client.get('/basic-verify',
80 headers={'Authorization': 'Basic ' + creds})
81 self.assertEqual(response.status_code, 403)
82 self.assertTrue('WWW-Authenticate' in response.headers)
83
84
85 class HTTPAuthTestCaseOldStyle(HTTPAuthTestCase):
86 use_old_style_callback = True
0 import unittest
1 import base64
2 from flask import Flask, Response
3 from flask_httpauth import HTTPBasicAuth
4
5
6 class HTTPAuthTestCase(unittest.TestCase):
7 responses = [
8 ['error', 401],
9 [('error', 403), 403],
10 [('error', 200), 200],
11 [Response('error'), 200],
12 [Response('error', 403), 403],
13 ]
14
15 def setUp(self):
16 app = Flask(__name__)
17 app.config['SECRET_KEY'] = 'my secret'
18
19 basic_verify_auth = HTTPBasicAuth()
20
21 @basic_verify_auth.verify_password
22 def basic_verify_auth_verify_password(username, password):
23 return False
24
25 @basic_verify_auth.error_handler
26 def error_handler():
27 self.assertIsNone(basic_verify_auth.current_user())
28 return self.error_response
29
30 @app.route('/')
31 @basic_verify_auth.login_required
32 def index():
33 return 'index'
34
35 self.app = app
36 self.basic_verify_auth = basic_verify_auth
37 self.client = app.test_client()
38
39 def test_default_status_code(self):
40 creds = base64.b64encode(b'foo:bar').decode('utf-8')
41
42 for r in self.responses:
43 self.error_response = r[0]
44 response = self.client.get(
45 '/', headers={'Authorization': 'Basic ' + creds})
46 self.assertEqual(response.status_code, r[1])
1010
1111 basic_auth = HTTPBasicAuth()
1212 token_auth = HTTPTokenAuth('MyToken')
13 multi_auth = MultiAuth(basic_auth, token_auth)
13 custom_token_auth = HTTPTokenAuth(header='X-Token')
14 multi_auth = MultiAuth(basic_auth, token_auth, custom_token_auth)
1415
1516 @basic_auth.verify_password
1617 def verify_password(username, password):
17 return username == 'john' and password == 'hello'
18 if username == 'john' and password == 'hello':
19 return 'john'
20
21 @basic_auth.get_user_roles
22 def get_basic_role(username):
23 if username == 'john':
24 return ['foo', 'bar']
1825
1926 @token_auth.verify_token
2027 def verify_token(token):
2128 return token == 'this-is-the-token!'
2229
30 @token_auth.get_user_roles
31 def get_token_role(auth):
32 if auth['token'] == 'this-is-the-token!':
33 return 'foo'
34 return
35
2336 @token_auth.error_handler
2437 def error_handler():
2538 return 'error', 401, {'WWW-Authenticate': 'MyToken realm="Foo"'}
39
40 @custom_token_auth.verify_token
41 def verify_custom_token(token):
42 return token == 'this-is-the-custom-token!'
43
44 @custom_token_auth.get_user_roles
45 def get_custom_token_role(auth):
46 if auth['token'] == 'this-is-the-custom-token!':
47 return 'foo'
48 return
2649
2750 @app.route('/')
2851 def index():
3154 @app.route('/protected')
3255 @multi_auth.login_required
3356 def auth_route():
34 return 'access granted'
57 return 'access granted:' + str(multi_auth.current_user())
58
59 @app.route('/protected-with-role')
60 @multi_auth.login_required(role='foo')
61 def auth_role_route():
62 return 'role access granted'
3563
3664 self.app = app
3765 self.client = app.test_client()
4775 creds = base64.b64encode(b'john:hello').decode('utf-8')
4876 response = self.client.get(
4977 '/protected', headers={'Authorization': 'Basic ' + creds})
50 self.assertEqual(response.data.decode('utf-8'), 'access granted')
78 self.assertEqual(response.data.decode('utf-8'), 'access granted:john')
5179
5280 def test_multi_auth_login_invalid_basic(self):
5381 creds = base64.b64encode(b'john:bye').decode('utf-8')
6290 response = self.client.get(
6391 '/protected', headers={'Authorization':
6492 'MyToken this-is-the-token!'})
65 self.assertEqual(response.data.decode('utf-8'), 'access granted')
93 self.assertEqual(response.data.decode('utf-8'), 'access granted:None')
6694
6795 def test_multi_auth_login_invalid_token(self):
6896 response = self.client.get(
72100 self.assertTrue('WWW-Authenticate' in response.headers)
73101 self.assertEqual(response.headers['WWW-Authenticate'],
74102 'MyToken realm="Foo"')
103
104 def test_multi_auth_login_valid_custom_token(self):
105 response = self.client.get(
106 '/protected', headers={'X-Token': 'this-is-the-custom-token!'})
107 self.assertEqual(response.data.decode('utf-8'), 'access granted:None')
108
109 def test_multi_auth_login_invalid_custom_token(self):
110 response = self.client.get(
111 '/protected', headers={'X-Token': 'this-is-not-the-token!'})
112 self.assertEqual(response.status_code, 401)
113 self.assertTrue('WWW-Authenticate' in response.headers)
114 self.assertEqual(response.headers['WWW-Authenticate'],
115 'Bearer realm="Authentication Required"')
75116
76117 def test_multi_auth_login_invalid_scheme(self):
77118 response = self.client.get(
85126 response = self.client.get(
86127 '/protected', headers={'Authorization': 'token-without-scheme'})
87128 self.assertEqual(response.status_code, 401)
129
130 def test_multi_auth_login_valid_basic_role(self):
131 creds = base64.b64encode(b'john:hello').decode('utf-8')
132 response = self.client.get(
133 '/protected-with-role', headers={'Authorization':
134 'Basic ' + creds})
135 self.assertEqual(response.data.decode('utf-8'), 'role access granted')
136
137 def test_multi_auth_login_valid_token_role(self):
138 response = self.client.get(
139 '/protected-with-role', headers={'Authorization':
140 'MyToken this-is-the-token!'})
141 self.assertEqual(response.data.decode('utf-8'), 'role access granted')
142
143 def test_multi_auth_login_valid_custom_token_role(self):
144 response = self.client.get(
145 '/protected-with-role', headers={'X-Token':
146 'this-is-the-custom-token!'})
147 self.assertEqual(response.data.decode('utf-8'), 'role access granted')
0 import unittest
1 import base64
2 from flask import Flask, g
3 from flask_httpauth import HTTPBasicAuth
4
5
6 class HTTPAuthTestCase(unittest.TestCase):
7 def setUp(self):
8 app = Flask(__name__)
9 app.config['SECRET_KEY'] = 'my secret'
10
11 roles_auth = HTTPBasicAuth()
12
13 @roles_auth.verify_password
14 def roles_auth_verify_password(username, password):
15 g.anon = False
16 if username == 'john':
17 return password == 'hello'
18 elif username == 'susan':
19 return password == 'bye'
20 elif username == 'cindy':
21 return password == 'byebye'
22 elif username == '':
23 g.anon = True
24 return True
25 return False
26
27 @roles_auth.get_user_roles
28 def get_user_roles(auth):
29 username = auth.username
30 if username == 'john':
31 return 'normal'
32 elif username == 'susan':
33 return ('normal', 'special')
34 elif username == 'cindy':
35 return None
36
37 @roles_auth.error_handler
38 def error_handler():
39 return 'error', 403 # use a custom error status
40
41 @app.route('/')
42 def index():
43 return 'index'
44
45 @app.route('/normal')
46 @roles_auth.login_required(role='normal')
47 def roles_auth_route_normal():
48 return 'normal:' + roles_auth.username()
49
50 @app.route('/special')
51 @roles_auth.login_required(role='special')
52 def roles_auth_route_special():
53 return 'special:' + roles_auth.username()
54
55 @app.route('/normal-or-special')
56 @roles_auth.login_required(role=('normal', 'special'))
57 def roles_auth_route_normal_or_special():
58 return 'normal_or_special:' + roles_auth.username()
59
60 @app.route('/normal-and-special')
61 @roles_auth.login_required(role=(('normal', 'special'),))
62 def roles_auth_route_normal_and_special():
63 return 'normal_and_special:' + roles_auth.username()
64
65 self.app = app
66 self.roles_auth = roles_auth
67 self.client = app.test_client()
68
69 def test_verify_roles_valid_normal_1(self):
70 creds = base64.b64encode(b'susan:bye').decode('utf-8')
71 response = self.client.get(
72 '/normal', headers={'Authorization': 'Basic ' + creds})
73 self.assertEqual(response.data, b'normal:susan')
74
75 def test_verify_roles_valid_normal_2(self):
76 creds = base64.b64encode(b'john:hello').decode('utf-8')
77 response = self.client.get(
78 '/normal', headers={'Authorization': 'Basic ' + creds})
79 self.assertEqual(response.data, b'normal:john')
80
81 def test_verify_auth_login_valid_special(self):
82 creds = base64.b64encode(b'susan:bye').decode('utf-8')
83 response = self.client.get(
84 '/special', headers={'Authorization': 'Basic ' + creds})
85 self.assertEqual(response.data, b'special:susan')
86
87 def test_verify_auth_login_invalid_special_1(self):
88 creds = base64.b64encode(b'john:hello').decode('utf-8')
89 response = self.client.get(
90 '/special', headers={'Authorization': 'Basic ' + creds})
91 self.assertEqual(response.status_code, 403)
92 self.assertTrue('WWW-Authenticate' in response.headers)
93
94 def test_verify_auth_login_invalid_special_2(self):
95 creds = base64.b64encode(b'cindy:byebye').decode('utf-8')
96 response = self.client.get(
97 '/special', headers={'Authorization': 'Basic ' + creds})
98 self.assertEqual(response.status_code, 403)
99 self.assertTrue('WWW-Authenticate' in response.headers)
100
101 def test_verify_auth_login_valid_normal_or_special_1(self):
102 creds = base64.b64encode(b'susan:bye').decode('utf-8')
103 response = self.client.get(
104 '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
105 self.assertEqual(response.data, b'normal_or_special:susan')
106
107 def test_verify_auth_login_valid_normal_or_special_2(self):
108 creds = base64.b64encode(b'john:hello').decode('utf-8')
109 response = self.client.get(
110 '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
111 self.assertEqual(response.data, b'normal_or_special:john')
112
113 def test_verify_auth_login_valid_normal_and_special_1(self):
114 creds = base64.b64encode(b'susan:bye').decode('utf-8')
115 response = self.client.get(
116 '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
117 self.assertEqual(response.data, b'normal_and_special:susan')
118
119 def test_verify_auth_login_valid_normal_and_special_2(self):
120 creds = base64.b64encode(b'john:hello').decode('utf-8')
121 response = self.client.get(
122 '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
123 self.assertEqual(response.status_code, 403)
124 self.assertTrue('WWW-Authenticate' in response.headers)
125
126 def test_verify_auth_login_invalid_password(self):
127 creds = base64.b64encode(b'john:bye').decode('utf-8')
128 response = self.client.get(
129 '/normal', headers={'Authorization': 'Basic ' + creds})
130 self.assertEqual(response.status_code, 403)
131 self.assertTrue('WWW-Authenticate' in response.headers)
0 import base64
01 import unittest
12 from flask import Flask
23 from flask_httpauth import HTTPTokenAuth
89 app.config['SECRET_KEY'] = 'my secret'
910
1011 token_auth = HTTPTokenAuth('MyToken')
12 token_auth2 = HTTPTokenAuth('Token', realm='foo')
13 token_auth3 = HTTPTokenAuth(header='X-API-Key')
1114
1215 @token_auth.verify_token
1316 def verify_token(token):
14 return token == 'this-is-the-token!'
17 if token == 'this-is-the-token!':
18 return 'user'
19
20 @token_auth3.verify_token
21 def verify_token3(token):
22 if token == 'this-is-the-token!':
23 return 'user'
1524
1625 @token_auth.error_handler
1726 def error_handler():
2433 @app.route('/protected')
2534 @token_auth.login_required
2635 def token_auth_route():
27 return 'token_auth'
36 return 'token_auth:' + token_auth.current_user()
37
38 @app.route('/protected-optional')
39 @token_auth.login_required(optional=True)
40 def token_auth_optional_route():
41 return 'token_auth:' + str(token_auth.current_user())
42
43 @app.route('/protected2')
44 @token_auth2.login_required
45 def token_auth_route2():
46 return 'token_auth2'
47
48 @app.route('/protected3')
49 @token_auth3.login_required
50 def token_auth_route3():
51 return 'token_auth3:' + token_auth3.current_user()
2852
2953 self.app = app
3054 self.token_auth = token_auth
4670 response = self.client.get(
4771 '/protected', headers={'Authorization':
4872 'MyToken this-is-the-token!'})
49 self.assertEqual(response.data.decode('utf-8'), 'token_auth')
73 self.assertEqual(response.data.decode('utf-8'), 'token_auth:user')
5074
5175 def test_token_auth_login_valid_different_case(self):
5276 response = self.client.get(
5377 '/protected', headers={'Authorization':
5478 'mytoken this-is-the-token!'})
55 self.assertEqual(response.data.decode('utf-8'), 'token_auth')
79 self.assertEqual(response.data.decode('utf-8'), 'token_auth:user')
80
81 def test_token_auth_login_optional(self):
82 response = self.client.get('/protected-optional')
83 self.assertEqual(response.data.decode('utf-8'), 'token_auth:None')
5684
5785 def test_token_auth_login_invalid_token(self):
5886 response = self.client.get(
80108 'MyToken realm="Foo"')
81109
82110 def test_token_auth_login_invalid_no_callback(self):
83 token_auth2 = HTTPTokenAuth('Token', realm='foo')
84
85 @self.app.route('/protected2')
86 @token_auth2.login_required
87 def token_auth_route2():
88 return 'token_auth2'
89
90111 response = self.client.get(
91112 '/protected2', headers={'Authorization':
92113 'Token this-is-the-token!'})
94115 self.assertTrue('WWW-Authenticate' in response.headers)
95116 self.assertEqual(response.headers['WWW-Authenticate'],
96117 'Token realm="foo"')
118
119 def test_token_auth_custom_header_valid_token(self):
120 response = self.client.get(
121 '/protected3', headers={'X-API-Key': 'this-is-the-token!'})
122 self.assertEqual(response.status_code, 200)
123 self.assertEqual(response.data.decode('utf-8'), 'token_auth3:user')
124
125 def test_token_auth_custom_header_invalid_token(self):
126 response = self.client.get(
127 '/protected3', headers={'X-API-Key': 'invalid-token-should-fail'})
128 self.assertEqual(response.status_code, 401)
129 self.assertTrue('WWW-Authenticate' in response.headers)
130
131 def test_token_auth_custom_header_invalid_header(self):
132 response = self.client.get(
133 '/protected3', headers={'API-Key': 'this-is-the-token!'})
134 self.assertEqual(response.status_code, 401)
135 self.assertTrue('WWW-Authenticate' in response.headers)
136 self.assertEqual(response.headers['WWW-Authenticate'],
137 'Bearer realm="Authentication Required"')
138
139 def test_token_auth_header_precedence(self):
140 basic_creds = base64.b64encode(b'susan:bye').decode('utf-8')
141 response = self.client.get(
142 '/protected3', headers={'Authorization': 'Basic ' + basic_creds,
143 'X-API-Key': 'this-is-the-token!'})
144 self.assertEqual(response.status_code, 200)
145 self.assertEqual(response.data.decode('utf-8'), 'token_auth3:user')
00 [tox]
1 envlist=flake8,py27,py34,py35,py36,pypy,docs,coverage
1 envlist=flake8,py36,py37,py38,py39,pypy3,docs,coverage
22 skip_missing_interpreters=True
3
4 [gh-actions]
5 python =
6 2.7: py27
7 3.6: py36
8 3.7: py37
9 3.8: py38
10 3.9: py39
11 pypy2: pypy2
12 pypy3: pypy3
313
414 [testenv]
515 commands=
616 coverage run --branch --include=flask_httpauth.py setup.py test
717 coverage report --show-missing
18 coverage xml -o coverage.xml
819 coverage erase
920 deps=
1021 coverage
1122
1223 [testenv:flake8]
13 basepython=python
1424 deps=
1525 flake8
1626 commands=
1727 flake8 --exclude=".*" --ignore=E402 flask_httpauth.py tests examples
1828
19 [testenv:py26]
20 basepython=python2.6
21
22 [testenv:py27]
23 basepython=python2.7
24
25 [testenv:py34]
26 basepython=python3.4
27
28 [testenv:py35]
29 basepython=python3.5
30
31 [testenv:py36]
32 basepython=python3.6
33
34 [testenv:pypy]
35 basepython=pypy
36
3729 [testenv:docs]
38 basepython=python2.7
3930 changedir=docs
4031 deps=
4132 sphinx
4334 make
4435 commands=
4536 make html
46
47 [testenv:coverage]
48 basepython=python
49 commands=
50 coverage run --branch --source=flask_httpauth.py setup.py test
51 coverage html
52 coverage erase