Codebase list python-flask-httpauth / 2edd515
New upstream version 4.5.0 Martina Ferrari 2 years ago
27 changed file(s) with 1853 addition(s) and 638 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 - main
5 pull_request:
6 branches:
7 - main
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.5.0** - 2021-10-25
3
4 - Support for Flask 2 async views ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/dc6de2dc9e1203e42d2763a0914e16ce96b74035))
5 - Do not read the request body [#138](https://github.com/miguelgrinberg/flask-httpauth/issues/138) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/d8b5e37c960263717117ae469f083d65e0f50972))
6 - Remove unused `flask.g` import in token authentication example [#137](https://github.com/miguelgrinberg/flask-httpauth/issues/137) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/7e5f9cf8167086e23e2d26c5d083a88e2e3b01ac)) (thanks **Jonas Sandström**!)
7 - Fixed documentation typo [#127](https://github.com/miguelgrinberg/flask-httpauth/issues/127) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/14fb9d5692bcd04a5679c38b1a3cb54ae38ced21)) (thanks **Reggie V**!)
8
9 **Release 4.4.0** - 2021-05-13
10
11 - 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**!)
12 - Drop Python 2 support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/e690ce56827de9d669718fa5d0fcda63112f8008))
13
14 **Release 4.3.0** - 2021-05-01
15
16 - 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))
17 - 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**!)
18 - Fixes typo [#116](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/116) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9b4659e47b7e05a630f91b7e9471feef5111b503)) (thanks **Renato Oliveira**!)
19 - Move builds to GitHub actions ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/588b277cae820a680199e0acf5a97e2be50c6f6c))
20
21 **Release 4.2.0** - 2020-11-16
22
23 - 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))
24 - 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**!)
25 - Remove python 3.5 and add python 3.9 to build ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/507a7c0bfdf7da3bfb6a0cff9624295cf1119986))
26
27 **Release 4.1.0** - 2020-06-04
28
29 - Basic authentication with custom scheme ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/1aaf872716cb46330fd49e89663da1a568e54f0b))
30
31 **Release 4.0.0** - 2020-04-26
32
33 - Return user object from verify callbacks ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/51748c24f5aa53175b0f2712b814f7ea581f04e4))
34 - New role authorization support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8178f6dd74dab47b993ba532dd12f0cfdb5799f1)) (thanks **gemerden**!)
35 - Add a custom token authorization header option ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/575b46ade7188152e1b82de84be949bf3f8a300b)) (thanks **Mohamed Feddad**!)
36 - Support an optional=True argument in `login_required` decorator ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8ecbb1157822360f5bdb24231fd50f25a6247620)) (thanks **Saif Almansoori**!)
37 - Pass HTTP status code to error callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fc8bcd6772b53ef5cc14cd4c6199d63cd2c71f30))
38 - More secure example of basic auth in the documentation ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0043e138cd99c7e9fa179ee30ad2283f9b8c704f))
39 - 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**!)
40
41 **Release 3.3.0** - 2019-05-19
42
43 - 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**!)
44 - 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**!)
45 - Documentation fix [#78](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/78) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c38c52326b78c91d4410f347abcd8bc49cc63ca4))
46 - Documentation improvements [#77](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/77) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ce5e5b4c9e8b748eba886ded5180e1e5d5036528))
47 - helper release script ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/7276d8db4b695645b01f3275addbec10418da63d))
48
49 **Release 3.2.4** - 2018-06-17
50
51 - Refactored HTTPAuth login_required [#74](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/74) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/68ee1e7a92355ba0f3f9b48c9489a67ab762e106)) (thanks **nestedsoftware**!)
52 - 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))
53 - 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**!)
54 - Fix documentation [#67](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/67) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9bd8f4b4f3574c7ef3e2fb9596bc9e9981275011)) (thanks **Eugene Rymarev**!)
55 - correct spelling mistake [#56](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/56) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f7c5bbd1b3a53080171bbdc5f1f1842f7a825f6a)) (thanks **Edward Betts**!)
56 - travis build fix for py36 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/6e7f32984bda8b82200793c1b3ec44ff3df3ad2b))
57
58 **Release 3.2.3** - 2017-06-05
59
60 - 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**!)
61
62 **Release 3.2.2** - 2017-01-30
63
64 - Validate authorization header in multi auth [#51](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/51) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/7a895d676a1b6998f58b61a177286b62dc2872f5))
65 - 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**!)
66
67 **Release 3.2.1** - 2016-09-04
68
69 - add `__version__` to package ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/d188450987f226568fe0cdee0b6d480b375af64a))
70 - 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))
71
72 **Release 3.2.0** - 2016-08-20
73
74 - 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**!)
75
76 **Release 3.1.2** - 2016-04-21
77
78 - Add robustness to password check ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/051fd88ee36a21a13255b4ec69e172c9ae4ad46d))
79
80 **Release 3.1.1** - 2016-03-24
81
82 - 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**!)
83 - add examples to flake8 build ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/61b1b71b3b29f2936ac6a2077883da1faeaad09f))
84 - Added multi auth tests ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c443e7ebcc227fd3690c2cf943d414087d7b931d))
85 - removed dead code ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4d2232e2a77f5e10e1731936f4ac64439049b220))
86
87 **Release 3.1.0** - 2016-03-13
88
89 - examples ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/609806a1c10264818e08ba0ce9b7babeaf101656))
90 - Added support for multiple authentication methods ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/6c3f94d9eda85b78a8c36cd5e05d6d9836bee2d0))
91 - Added change log ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/8b427b962114a6ef13badaf8f2f1b396c540955a))
92 - Add additional token auth test ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/29edb1948f086babbd1a9e0c87a0a35c05f0a63b))
93
94 **Release 3.0.2** - 2016-03-12
95
96 - Let callback decide what to do when authentication type does not match ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/b942f980970d2e387a80f68de4ea2bb8728b149c))
97
98 **Release 3.0.1** - 2016-03-09
99
100 - Catching exception when Authorization header is empty ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/88d073e05b56b810feb447d1c9cee7a9a9ac9b1b)) (thanks **Kari Hreinsson**!)
101 - Documentation fix, validate_token() -> verify_token() ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/f4b41d736311638978c95c9b5fd458063a009280)) (thanks **Kari Hreinsson**!)
102
103 **Release 3.0.0** - 2016-03-07
104
105 - documentation for new token auth ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c0ae42df517a45be87f419cbb7f8002228a1e83c))
106 - switch travis build to use tox ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/00fdebce667e1dbbc5b342a21804cb6ab3b4f417))
107 - token auth support, plus test reorg ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/aac866de14c68a4d17d3098f8e96102e837add1d))
108 - Added explicity Python 2 & 3 version classifiers to package ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/a6f50e7be6f13bb814c47fe8a3a44cd34138f87e))
109
110 **Release 2.7.1** - 2016-02-07
111
112 - 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**!)
113 - Add Install Notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/0ff88331c9724999d8f283d79fe95de949e64438)) (thanks **Michael Washburn Jr**!)
114 - 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**!)
115
116 **Release 2.7.0** - 2015-09-20
117
118 - Support custom authentication scheme and realm ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/bf12f959bba24a2f3d7d799d1b57ef3a5f1001e8))
119
120 **Release 2.6.0** - 2015-08-23
121
122 - Added information on how to implement digest authentication securely ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fb02625ca0f7694d8e744e0b3d2c8d4ffcc4d7cd))
123 - 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**!)
124 - fixed tests to work with python 2.6 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5e85b27a06285fb5bd591f9f65a8a0bebc4a34f2))
125 - added travis ci badge ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ef354fd07abd08137beba6362debdcb4ef23baf6))
126
127 **release 2.5.0** - 2015-04-26
128
129 - documentation changes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5c98ed8370355a60e22e017a79d5575adadb9c07))
130 - documentation for stored ha1 feature ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/37fd9288abb4f11abf9f93303d1bce4e6cfc3c19))
131 - Include notes for nginx ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/ed8b4a3c954240cde0c66af3d6dae37df48ba976)) (thanks **Erik Stephens**!)
132 - Include notes for nginx as well ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5bccbae862cbf1ca7d02f717b076aca86b1456e5)) (thanks **Erik Stephens**!)
133 - Update docs with WSGI notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/9ddd55f0bcb793a49675274dc22ae15122a8a1ff)) (thanks **Erik Stephens**!)
134 - Update README with WSGI notes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/af5fa26dc73d401de7760ba3dcd61828c2e548dd)) (thanks **Erik Stephens**!)
135 - 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**!)
136
137 **release 2.4.0** - 2015-03-02
138
139 - Support anonymous users in verify_password callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5c5396bbb7af540a7aff786ce3282657566045f2))
140 - Add HA1 generation function to HTTPDigestAuth class ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4f4aed3ed3fa5e96a1a052e4414f14d1fc49b8bb)) (thanks **Pawel Szczurko**!)
141 - Fix unit test url routes ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/a490a521a17313ce82bfe886912b1620166eb6dd)) (thanks **Pawel Szczurko**!)
142 - 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**!)
143 - removed extra strip() calls in unit tests ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/fc34cc5020168ca3824cc4a740b2010bb3132abf))
144
145 **release 2.3.0** - 2014-09-23
146
147 - pep8 ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/4657d5b37e50483ecccabf0887ea417d3b94ea0a))
148 - 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**!)
149 - Ignore authentication headers for OPTIONS ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/044b7d4a44425a4b9d02280b80988e8986641a0d)) (thanks **Henrique Carvalho Alves**!)
150
151 **release 2.2.1** - 2014-03-17
152
153 - [#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))
154 - [#5](https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/5) ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/051195d68d8aaf6d9e53d14d69a59afd84f24821))
155 - 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**!)
156 - custom password verification callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/33d60f21a6e64f1b2df24ea5035164110979d8ab))
157
158 **version 2.1.0** - 2013-09-28
159
160 - pass the username to the hash password callback ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/13075ec4dbe4cb733f4f433e1e25e8a180fce1f6))
161
162 **Release 2.0.0** - 2013-09-26
163
164 - changed auth.username to auth.username() ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/5168a5f703552ec092e3fef9e087052e35fb6ff0))
165 - 2.0 documentation update ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/e668f59cb674e45891b7d9548e5af3028f2fd22d))
166
167 **Release 1.1.0** - 2013-08-30
168
169 - python 3 support ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c13ff0a4c1e5922a635ea7c877a2ef6079ddb4e6))
170 - documentation update ([commit](https://github.com/miguelgrinberg/Flask-HTTPAuth/commit/c468e1c084e5c25dcaa85b45e5abeb88fbc09420))
171
172 **Release 1.0.0** - 2013-07-27
173
174 - 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)
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 function 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
132 from flask import Flask, g
110 from flask import Flask
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__':
+0
-285
flask_httpauth.py less more
0 """
1 flask_httpauth
2 ==================
3
4 This module provides Basic and Digest HTTP authentication for Flask routes.
5
6 :copyright: (C) 2014 by Miguel Grinberg.
7 :license: MIT, see LICENSE for more details.
8 """
9
10 from functools import wraps
11 from hashlib import md5
12 from random import Random, SystemRandom
13 from flask import request, make_response, session
14 from werkzeug.datastructures import Authorization
15
16 __version__ = '3.2.4'
17
18
19 class HTTPAuth(object):
20 def __init__(self, scheme=None, realm=None):
21 self.scheme = scheme
22 self.realm = realm or "Authentication Required"
23 self.get_password_callback = None
24 self.auth_error_callback = None
25
26 def default_get_password(username):
27 return None
28
29 def default_auth_error():
30 return "Unauthorized Access"
31
32 self.get_password(default_get_password)
33 self.error_handler(default_auth_error)
34
35 def get_password(self, f):
36 self.get_password_callback = f
37 return f
38
39 def error_handler(self, f):
40 @wraps(f)
41 def decorated(*args, **kwargs):
42 res = f(*args, **kwargs)
43 res = make_response(res)
44 if res.status_code == 200:
45 # if user didn't set status code, use 401
46 res.status_code = 401
47 if 'WWW-Authenticate' not in res.headers.keys():
48 res.headers['WWW-Authenticate'] = self.authenticate_header()
49 return res
50 self.auth_error_callback = decorated
51 return decorated
52
53 def authenticate_header(self):
54 return '{0} realm="{1}"'.format(self.scheme, self.realm)
55
56 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
69
70 # if the auth type does not match, we act as if there is no auth
71 # this is better than failing directly, as it allows the callback
72 # to handle special cases, like supporting multiple auth types
73 if auth is not None and auth.type.lower() != self.scheme.lower():
74 auth = None
75
76 return auth
77
78 def get_auth_password(self, auth):
79 password = None
80
81 if auth and auth.username:
82 password = self.get_password_callback(auth.username)
83
84 return password
85
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
105
106 def username(self):
107 if not request.authorization:
108 return ""
109 return request.authorization.username
110
111
112 class HTTPBasicAuth(HTTPAuth):
113 def __init__(self, scheme=None, realm=None):
114 super(HTTPBasicAuth, self).__init__(scheme or 'Basic', realm)
115
116 self.hash_password_callback = None
117 self.verify_password_callback = None
118
119 def hash_password(self, f):
120 self.hash_password_callback = f
121 return f
122
123 def verify_password(self, f):
124 self.verify_password_callback = f
125 return f
126
127 def authenticate(self, auth, stored_password):
128 if auth:
129 username = auth.username
130 client_password = auth.password
131 else:
132 username = ""
133 client_password = ""
134 if self.verify_password_callback:
135 return self.verify_password_callback(username, client_password)
136 if not auth:
137 return False
138 if self.hash_password_callback:
139 try:
140 client_password = self.hash_password_callback(client_password)
141 except TypeError:
142 client_password = self.hash_password_callback(username,
143 client_password)
144 return client_password is not None and \
145 client_password == stored_password
146
147
148 class HTTPDigestAuth(HTTPAuth):
149 def __init__(self, scheme=None, realm=None, use_ha1_pw=False):
150 super(HTTPDigestAuth, self).__init__(scheme or 'Digest', realm)
151 self.use_ha1_pw = use_ha1_pw
152 self.random = SystemRandom()
153 try:
154 self.random.random()
155 except NotImplementedError: # pragma: no cover
156 self.random = Random()
157
158 self.generate_nonce_callback = None
159 self.verify_nonce_callback = None
160 self.generate_opaque_callback = None
161 self.verify_opaque_callback = None
162
163 def _generate_random():
164 return md5(str(self.random.random()).encode('utf-8')).hexdigest()
165
166 def default_generate_nonce():
167 session["auth_nonce"] = _generate_random()
168 return session["auth_nonce"]
169
170 def default_verify_nonce(nonce):
171 return nonce == session.get("auth_nonce")
172
173 def default_generate_opaque():
174 session["auth_opaque"] = _generate_random()
175 return session["auth_opaque"]
176
177 def default_verify_opaque(opaque):
178 return opaque == session.get("auth_opaque")
179
180 self.generate_nonce(default_generate_nonce)
181 self.generate_opaque(default_generate_opaque)
182 self.verify_nonce(default_verify_nonce)
183 self.verify_opaque(default_verify_opaque)
184
185 def generate_nonce(self, f):
186 self.generate_nonce_callback = f
187 return f
188
189 def verify_nonce(self, f):
190 self.verify_nonce_callback = f
191 return f
192
193 def generate_opaque(self, f):
194 self.generate_opaque_callback = f
195 return f
196
197 def verify_opaque(self, f):
198 self.verify_opaque_callback = f
199 return f
200
201 def get_nonce(self):
202 return self.generate_nonce_callback()
203
204 def get_opaque(self):
205 return self.generate_opaque_callback()
206
207 def generate_ha1(self, username, password):
208 a1 = username + ":" + self.realm + ":" + password
209 a1 = a1.encode('utf-8')
210 return md5(a1).hexdigest()
211
212 def authenticate_header(self):
213 nonce = self.get_nonce()
214 opaque = self.get_opaque()
215 return '{0} realm="{1}",nonce="{2}",opaque="{3}"'.format(
216 self.scheme, self.realm, nonce,
217 opaque)
218
219 def authenticate(self, auth, stored_password_or_ha1):
220 if not auth or not auth.username or not auth.realm or not auth.uri \
221 or not auth.nonce or not auth.response \
222 or not stored_password_or_ha1:
223 return False
224 if not(self.verify_nonce_callback(auth.nonce)) or \
225 not(self.verify_opaque_callback(auth.opaque)):
226 return False
227 if self.use_ha1_pw:
228 ha1 = stored_password_or_ha1
229 else:
230 a1 = auth.username + ":" + auth.realm + ":" + \
231 stored_password_or_ha1
232 ha1 = md5(a1.encode('utf-8')).hexdigest()
233 a2 = request.method + ":" + auth.uri
234 ha2 = md5(a2.encode('utf-8')).hexdigest()
235 a3 = ha1 + ":" + auth.nonce + ":" + ha2
236 response = md5(a3.encode('utf-8')).hexdigest()
237 return response == auth.response
238
239
240 class HTTPTokenAuth(HTTPAuth):
241 def __init__(self, scheme='Bearer', realm=None):
242 super(HTTPTokenAuth, self).__init__(scheme, realm)
243
244 self.verify_token_callback = None
245
246 def verify_token(self, f):
247 self.verify_token_callback = f
248 return f
249
250 def authenticate(self, auth, stored_password):
251 if auth:
252 token = auth['token']
253 else:
254 token = ""
255 if self.verify_token_callback:
256 return self.verify_token_callback(token)
257 return False
258
259
260 class MultiAuth(object):
261 def __init__(self, main_auth, *args):
262 self.main_auth = main_auth
263 self.additional_auth = args
264
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:
277 for auth in self.additional_auth:
278 if auth.scheme == scheme:
279 selected_auth = auth
280 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
0 [build-system]
1 requires = [
2 "setuptools>=42",
3 "wheel"
4 ]
5 build-backend = "setuptools.build_meta"
0 [metadata]
1 name = Flask-HTTPAuth
2 version = 4.5.0
3 author = Miguel Grinberg
4 author_email = miguel.grinberg@gmail.com
5 description = HTTP authentication for Flask routes
6 long_description = file: README.md
7 long_description_content_type = text/markdown
8 url = https://github.com/miguelgrinberg/flask-httpauth
9 project_urls =
10 Bug Tracker = https://github.com/miguelgrinberg/flask-httpauth/issues
11 classifiers =
12 Environment :: Web Environment
13 Intended Audience :: Developers
14 Programming Language :: Python :: 3
15 Programming Language :: Python :: Implementation :: MicroPython
16 License :: OSI Approved :: MIT License
17 Operating System :: OS Independent
18
19 [options]
20 zip_safe = False
21 include_package_data = True
22 install_requires =
23 flask
24 package_dir =
25 = src
26 py_modules =
27 flask_httpauth
0 """
1 Flask-HTTPAuth
2 --------------
0 import setuptools
31
4 Basic and Digest HTTP authentication for Flask routes.
5 """
6 import re
7 from setuptools import setup
8
9 with open('flask_httpauth.py', 'r') as f:
10 version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
11 f.read(), re.MULTILINE).group(1)
12
13 setup(
14 name='Flask-HTTPAuth',
15 version=version,
16 url='http://github.com/miguelgrinberg/flask-httpauth/',
17 license='MIT',
18 author='Miguel Grinberg',
19 author_email='miguelgrinberg50@gmail.com',
20 description='Basic and Digest HTTP authentication for Flask routes',
21 long_description=__doc__,
22 py_modules=['flask_httpauth'],
23 zip_safe=False,
24 include_package_data=True,
25 platforms='any',
26 install_requires=[
27 'Flask'
28 ],
29 test_suite="tests",
30 classifiers=[
31 'Environment :: Web Environment',
32 'Intended Audience :: Developers',
33 'License :: OSI Approved :: MIT License',
34 'Operating System :: OS Independent',
35 'Programming Language :: Python',
36 'Programming Language :: Python :: 2',
37 'Programming Language :: Python :: 3',
38 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
39 'Topic :: Software Development :: Libraries :: Python Modules'
40 ]
41 )
2 setuptools.setup()
0 """
1 flask_httpauth
2 ==================
3
4 This module provides Basic and Digest HTTP authentication for Flask routes.
5
6 :copyright: (C) 2014 by Miguel Grinberg.
7 :license: MIT, see LICENSE for more details.
8 """
9 import hmac
10 from base64 import b64decode
11 from functools import wraps
12 from hashlib import md5
13 from random import Random, SystemRandom
14 from flask import request, make_response, session, g, Response, current_app
15 from werkzeug.datastructures import Authorization
16
17
18 class HTTPAuth(object):
19 def __init__(self, scheme=None, realm=None, header=None):
20 self.scheme = scheme
21 self.realm = realm or "Authentication Required"
22 self.header = header
23 self.get_password_callback = None
24 self.get_user_roles_callback = None
25 self.auth_error_callback = None
26
27 def default_get_password(username):
28 return None
29
30 def default_auth_error(status):
31 return "Unauthorized Access", status
32
33 self.get_password(default_get_password)
34 self.error_handler(default_auth_error)
35
36 def is_compatible_auth(self, headers):
37 if self.header is None or self.header == 'Authorization':
38 try:
39 scheme, _ = request.headers.get('Authorization', '').split(
40 None, 1)
41 except ValueError:
42 # malformed Authorization header
43 return False
44 return scheme == self.scheme
45 else:
46 return self.header in headers
47
48 def get_password(self, f):
49 self.get_password_callback = f
50 return f
51
52 def get_user_roles(self, f):
53 self.get_user_roles_callback = f
54 return f
55
56 def error_handler(self, f):
57 @wraps(f)
58 def decorated(*args, **kwargs):
59 res = self.ensure_sync(f)(*args, **kwargs)
60 check_status_code = not isinstance(res, (tuple, Response))
61 res = make_response(res)
62 if check_status_code and res.status_code == 200:
63 # if user didn't set status code, use 401
64 res.status_code = 401
65 if 'WWW-Authenticate' not in res.headers.keys():
66 res.headers['WWW-Authenticate'] = self.authenticate_header()
67 return res
68 self.auth_error_callback = decorated
69 return decorated
70
71 def authenticate_header(self):
72 return '{0} realm="{1}"'.format(self.scheme, self.realm)
73
74 def get_auth(self):
75 auth = None
76 if self.header is None or self.header == 'Authorization':
77 auth = request.authorization
78 if auth is None and 'Authorization' in request.headers:
79 # Flask/Werkzeug do not recognize any authentication types
80 # other than Basic or Digest, so here we parse the header by
81 # hand
82 try:
83 auth_type, token = request.headers['Authorization'].split(
84 None, 1)
85 auth = Authorization(auth_type, {'token': token})
86 except (ValueError, KeyError):
87 # The Authorization header is either empty or has no token
88 pass
89 elif self.header in request.headers:
90 # using a custom header, so the entire value of the header is
91 # assumed to be a token
92 auth = Authorization(self.scheme,
93 {'token': request.headers[self.header]})
94
95 # if the auth type does not match, we act as if there is no auth
96 # this is better than failing directly, as it allows the callback
97 # to handle special cases, like supporting multiple auth types
98 if auth is not None and auth.type.lower() != self.scheme.lower():
99 auth = None
100
101 return auth
102
103 def get_auth_password(self, auth):
104 password = None
105
106 if auth and auth.username:
107 password = self.ensure_sync(self.get_password_callback)(
108 auth.username)
109
110 return password
111
112 def authorize(self, role, user, auth):
113 if role is None:
114 return True
115 if isinstance(role, (list, tuple)):
116 roles = role
117 else:
118 roles = [role]
119 if user is True:
120 user = auth
121 if self.get_user_roles_callback is None: # pragma: no cover
122 raise ValueError('get_user_roles callback is not defined')
123 user_roles = self.ensure_sync(self.get_user_roles_callback)(user)
124 if user_roles is None:
125 user_roles = {}
126 elif not isinstance(user_roles, (list, tuple)):
127 user_roles = {user_roles}
128 else:
129 user_roles = set(user_roles)
130 for role in roles:
131 if isinstance(role, (list, tuple)):
132 role = set(role)
133 if role & user_roles == role:
134 return True
135 elif role in user_roles:
136 return True
137
138 def login_required(self, f=None, role=None, optional=None):
139 if f is not None and \
140 (role is not None or optional is not None): # pragma: no cover
141 raise ValueError(
142 'role and optional are the only supported arguments')
143
144 def login_required_internal(f):
145 @wraps(f)
146 def decorated(*args, **kwargs):
147 auth = self.get_auth()
148
149 # Flask normally handles OPTIONS requests on its own, but in
150 # the case it is configured to forward those to the
151 # application, we need to ignore authentication headers and
152 # let the request through to avoid unwanted interactions with
153 # CORS.
154 if request.method != 'OPTIONS': # pragma: no cover
155 password = self.get_auth_password(auth)
156
157 status = None
158 user = self.authenticate(auth, password)
159 if user in (False, None):
160 status = 401
161 elif not self.authorize(role, user, auth):
162 status = 403
163 if not optional and status:
164 try:
165 return self.auth_error_callback(status)
166 except TypeError:
167 return self.auth_error_callback()
168
169 g.flask_httpauth_user = user if user is not True \
170 else auth.username if auth else None
171 return self.ensure_sync(f)(*args, **kwargs)
172 return decorated
173
174 if f:
175 return login_required_internal(f)
176 return login_required_internal
177
178 def username(self):
179 auth = self.get_auth()
180 if not auth:
181 return ""
182 return auth.username
183
184 def current_user(self):
185 if hasattr(g, 'flask_httpauth_user'):
186 return g.flask_httpauth_user
187
188 def ensure_sync(self, f):
189 try:
190 return current_app.ensure_sync(f)
191 except AttributeError: # pragma: no cover
192 return f
193
194
195 class HTTPBasicAuth(HTTPAuth):
196 def __init__(self, scheme=None, realm=None):
197 super(HTTPBasicAuth, self).__init__(scheme or 'Basic', realm)
198
199 self.hash_password_callback = None
200 self.verify_password_callback = None
201
202 def hash_password(self, f):
203 self.hash_password_callback = f
204 return f
205
206 def verify_password(self, f):
207 self.verify_password_callback = f
208 return f
209
210 def get_auth(self):
211 # this version of the Authorization header parser is more flexible
212 # than Werkzeug's, as it also accepts other schemes besides "Basic"
213 header = self.header or 'Authorization'
214 if header not in request.headers:
215 return None
216 value = request.headers[header].encode('utf-8')
217 try:
218 scheme, credentials = value.split(b' ', 1)
219 username, password = b64decode(credentials).split(b':', 1)
220 except (ValueError, TypeError):
221 return None
222 try:
223 username = username.decode('utf-8')
224 password = password.decode('utf-8')
225 except UnicodeDecodeError:
226 username = None
227 password = None
228 return Authorization(
229 scheme, {'username': username, 'password': password})
230
231 def authenticate(self, auth, stored_password):
232 if auth:
233 username = auth.username
234 client_password = auth.password
235 else:
236 username = ""
237 client_password = ""
238 if self.verify_password_callback:
239 return self.ensure_sync(self.verify_password_callback)(
240 username, client_password)
241 if not auth:
242 return
243 if self.hash_password_callback:
244 try:
245 client_password = self.ensure_sync(
246 self.hash_password_callback)(client_password)
247 except TypeError:
248 client_password = self.ensure_sync(
249 self.hash_password_callback)(username, client_password)
250 return auth.username if client_password is not None and \
251 stored_password is not None and \
252 hmac.compare_digest(client_password, stored_password) else None
253
254
255 class HTTPDigestAuth(HTTPAuth):
256 def __init__(self, scheme=None, realm=None, use_ha1_pw=False):
257 super(HTTPDigestAuth, self).__init__(scheme or 'Digest', realm)
258 self.use_ha1_pw = use_ha1_pw
259 self.random = SystemRandom()
260 try:
261 self.random.random()
262 except NotImplementedError: # pragma: no cover
263 self.random = Random()
264
265 self.generate_nonce_callback = None
266 self.verify_nonce_callback = None
267 self.generate_opaque_callback = None
268 self.verify_opaque_callback = None
269
270 def _generate_random():
271 return md5(str(self.random.random()).encode('utf-8')).hexdigest()
272
273 def default_generate_nonce():
274 session["auth_nonce"] = _generate_random()
275 return session["auth_nonce"]
276
277 def default_verify_nonce(nonce):
278 session_nonce = session.get("auth_nonce")
279 if nonce is None or session_nonce is None:
280 return False
281 return hmac.compare_digest(nonce, session_nonce)
282
283 def default_generate_opaque():
284 session["auth_opaque"] = _generate_random()
285 return session["auth_opaque"]
286
287 def default_verify_opaque(opaque):
288 session_opaque = session.get("auth_opaque")
289 if opaque is None or session_opaque is None: # pragma: no cover
290 return False
291 return hmac.compare_digest(opaque, session_opaque)
292
293 self.generate_nonce(default_generate_nonce)
294 self.generate_opaque(default_generate_opaque)
295 self.verify_nonce(default_verify_nonce)
296 self.verify_opaque(default_verify_opaque)
297
298 def generate_nonce(self, f):
299 self.generate_nonce_callback = f
300 return f
301
302 def verify_nonce(self, f):
303 self.verify_nonce_callback = f
304 return f
305
306 def generate_opaque(self, f):
307 self.generate_opaque_callback = f
308 return f
309
310 def verify_opaque(self, f):
311 self.verify_opaque_callback = f
312 return f
313
314 def get_nonce(self):
315 return self.generate_nonce_callback()
316
317 def get_opaque(self):
318 return self.generate_opaque_callback()
319
320 def generate_ha1(self, username, password):
321 a1 = username + ":" + self.realm + ":" + password
322 a1 = a1.encode('utf-8')
323 return md5(a1).hexdigest()
324
325 def authenticate_header(self):
326 nonce = self.get_nonce()
327 opaque = self.get_opaque()
328 return '{0} realm="{1}",nonce="{2}",opaque="{3}"'.format(
329 self.scheme, self.realm, nonce,
330 opaque)
331
332 def authenticate(self, auth, stored_password_or_ha1):
333 if not auth or not auth.username or not auth.realm or not auth.uri \
334 or not auth.nonce or not auth.response \
335 or not stored_password_or_ha1:
336 return False
337 if not(self.verify_nonce_callback(auth.nonce)) or \
338 not(self.verify_opaque_callback(auth.opaque)):
339 return False
340 if self.use_ha1_pw:
341 ha1 = stored_password_or_ha1
342 else:
343 a1 = auth.username + ":" + auth.realm + ":" + \
344 stored_password_or_ha1
345 ha1 = md5(a1.encode('utf-8')).hexdigest()
346 a2 = request.method + ":" + auth.uri
347 ha2 = md5(a2.encode('utf-8')).hexdigest()
348 a3 = ha1 + ":" + auth.nonce + ":" + ha2
349 response = md5(a3.encode('utf-8')).hexdigest()
350 return hmac.compare_digest(response, auth.response)
351
352
353 class HTTPTokenAuth(HTTPAuth):
354 def __init__(self, scheme='Bearer', realm=None, header=None):
355 super(HTTPTokenAuth, self).__init__(scheme, realm, header)
356
357 self.verify_token_callback = None
358
359 def verify_token(self, f):
360 self.verify_token_callback = f
361 return f
362
363 def authenticate(self, auth, stored_password):
364 if auth:
365 token = auth['token']
366 else:
367 token = ""
368 if self.verify_token_callback:
369 return self.ensure_sync(self.verify_token_callback)(token)
370
371
372 class MultiAuth(object):
373 def __init__(self, main_auth, *args):
374 self.main_auth = main_auth
375 self.additional_auth = args
376
377 def login_required(self, f=None, role=None, optional=None):
378 if f is not None and \
379 (role is not None or optional is not None): # pragma: no cover
380 raise ValueError(
381 'role and optional are the only supported arguments')
382
383 def login_required_internal(f):
384 @wraps(f)
385 def decorated(*args, **kwargs):
386 selected_auth = self.main_auth
387 if not self.main_auth.is_compatible_auth(request.headers):
388 for auth in self.additional_auth:
389 if auth.is_compatible_auth(request.headers):
390 selected_auth = auth
391 break
392 return selected_auth.login_required(
393 role=role, optional=optional)(f)(*args, **kwargs)
394 return decorated
395
396 if f:
397 return login_required_internal(f)
398 return login_required_internal
399
400 def current_user(self):
401 if hasattr(g, 'flask_httpauth_user'): # pragma: no cover
402 return g.flask_httpauth_user
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 sys
1 import unittest
2 import base64
3 from flask import Flask, g
4 from flask_httpauth import HTTPBasicAuth
5 import pytest
6
7
8 @pytest.mark.skipif(sys.version_info < (3, 7), reason='requires python3.7')
9 class HTTPAuthTestCase(unittest.TestCase):
10 use_old_style_callback = False
11
12 def setUp(self):
13 app = Flask(__name__)
14 app.config['SECRET_KEY'] = 'my secret'
15
16 basic_verify_auth = HTTPBasicAuth()
17
18 @basic_verify_auth.verify_password
19 async def basic_verify_auth_verify_password(username, password):
20 if self.use_old_style_callback:
21 g.anon = False
22 if username == 'john':
23 return password == 'hello'
24 elif username == 'susan':
25 return password == 'bye'
26 elif username == '':
27 g.anon = True
28 return True
29 return False
30 else:
31 g.anon = False
32 if username == 'john' and password == 'hello':
33 return 'john'
34 elif username == 'susan' and password == 'bye':
35 return 'susan'
36 elif username == '':
37 g.anon = True
38 return ''
39
40 @basic_verify_auth.error_handler
41 async def error_handler():
42 self.assertIsNone(basic_verify_auth.current_user())
43 return 'error', 403 # use a custom error status
44
45 @app.route('/')
46 async def index():
47 return 'index'
48
49 @app.route('/basic-verify')
50 @basic_verify_auth.login_required
51 async def basic_verify_auth_route():
52 if self.use_old_style_callback:
53 return 'basic_verify_auth:' + basic_verify_auth.username() + \
54 ' anon:' + str(g.anon)
55 else:
56 return 'basic_verify_auth:' + \
57 basic_verify_auth.current_user() + ' anon:' + str(g.anon)
58
59 self.app = app
60 self.basic_verify_auth = basic_verify_auth
61 self.client = app.test_client()
62
63 def test_verify_auth_login_valid(self):
64 creds = base64.b64encode(b'susan:bye').decode('utf-8')
65 response = self.client.get(
66 '/basic-verify', headers={'Authorization': 'Basic ' + creds})
67 self.assertEqual(response.data, b'basic_verify_auth:susan anon:False')
68
69 def test_verify_auth_login_empty(self):
70 response = self.client.get('/basic-verify')
71 self.assertEqual(response.data, b'basic_verify_auth: anon:True')
72
73 def test_verify_auth_login_invalid(self):
74 creds = base64.b64encode(b'john:bye').decode('utf-8')
75 response = self.client.get(
76 '/basic-verify', headers={'Authorization': 'Basic ' + creds})
77 self.assertEqual(response.status_code, 403)
78 self.assertTrue('WWW-Authenticate' in response.headers)
79
80 def test_verify_auth_login_malformed_password(self):
81 creds = 'eyJhbGciOieyJp=='
82 response = self.client.get('/basic-verify',
83 headers={'Authorization': 'Basic ' + creds})
84 self.assertEqual(response.status_code, 403)
85 self.assertTrue('WWW-Authenticate' in response.headers)
86
87
88 class HTTPAuthTestCaseOldStyle(HTTPAuthTestCase):
89 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 base64
1 import sys
2 import unittest
3 from flask import Flask
4 from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth
5 import pytest
6
7
8 @pytest.mark.skipif(sys.version_info < (3, 7), reason='requires python3.7')
9 class HTTPAuthTestCase(unittest.TestCase):
10 def setUp(self):
11 app = Flask(__name__)
12 app.config['SECRET_KEY'] = 'my secret'
13
14 basic_auth = HTTPBasicAuth()
15 token_auth = HTTPTokenAuth('MyToken')
16 custom_token_auth = HTTPTokenAuth(header='X-Token')
17 multi_auth = MultiAuth(basic_auth, token_auth, custom_token_auth)
18
19 @basic_auth.verify_password
20 async def verify_password(username, password):
21 if username == 'john' and password == 'hello':
22 return 'john'
23
24 @basic_auth.get_user_roles
25 async def get_basic_role(username):
26 if username == 'john':
27 return ['foo', 'bar']
28
29 @token_auth.verify_token
30 async def verify_token(token):
31 return token == 'this-is-the-token!'
32
33 @token_auth.get_user_roles
34 async def get_token_role(auth):
35 if auth['token'] == 'this-is-the-token!':
36 return 'foo'
37 return
38
39 @token_auth.error_handler
40 async def error_handler():
41 return 'error', 401, {'WWW-Authenticate': 'MyToken realm="Foo"'}
42
43 @custom_token_auth.verify_token
44 async def verify_custom_token(token):
45 return token == 'this-is-the-custom-token!'
46
47 @custom_token_auth.get_user_roles
48 async def get_custom_token_role(auth):
49 if auth['token'] == 'this-is-the-custom-token!':
50 return 'foo'
51 return
52
53 @app.route('/')
54 async def index():
55 return 'index'
56
57 @app.route('/protected')
58 @multi_auth.login_required
59 async def auth_route():
60 return 'access granted:' + str(multi_auth.current_user())
61
62 @app.route('/protected-with-role')
63 @multi_auth.login_required(role='foo')
64 async def auth_role_route():
65 return 'role access granted'
66
67 self.app = app
68 self.client = app.test_client()
69
70 def test_multi_auth_prompt(self):
71 response = self.client.get('/protected')
72 self.assertEqual(response.status_code, 401)
73 self.assertTrue('WWW-Authenticate' in response.headers)
74 self.assertEqual(response.headers['WWW-Authenticate'],
75 'Basic realm="Authentication Required"')
76
77 def test_multi_auth_login_valid_basic(self):
78 creds = base64.b64encode(b'john:hello').decode('utf-8')
79 response = self.client.get(
80 '/protected', headers={'Authorization': 'Basic ' + creds})
81 self.assertEqual(response.data.decode('utf-8'), 'access granted:john')
82
83 def test_multi_auth_login_invalid_basic(self):
84 creds = base64.b64encode(b'john:bye').decode('utf-8')
85 response = self.client.get(
86 '/protected', headers={'Authorization': 'Basic ' + creds})
87 self.assertEqual(response.status_code, 401)
88 self.assertTrue('WWW-Authenticate' in response.headers)
89 self.assertEqual(response.headers['WWW-Authenticate'],
90 'Basic realm="Authentication Required"')
91
92 def test_multi_auth_login_valid_token(self):
93 response = self.client.get(
94 '/protected', headers={'Authorization':
95 'MyToken this-is-the-token!'})
96 self.assertEqual(response.data.decode('utf-8'), 'access granted:None')
97
98 def test_multi_auth_login_invalid_token(self):
99 response = self.client.get(
100 '/protected', headers={'Authorization':
101 'MyToken this-is-not-the-token!'})
102 self.assertEqual(response.status_code, 401)
103 self.assertTrue('WWW-Authenticate' in response.headers)
104 self.assertEqual(response.headers['WWW-Authenticate'],
105 'MyToken realm="Foo"')
106
107 def test_multi_auth_login_valid_custom_token(self):
108 response = self.client.get(
109 '/protected', headers={'X-Token': 'this-is-the-custom-token!'})
110 self.assertEqual(response.data.decode('utf-8'), 'access granted:None')
111
112 def test_multi_auth_login_invalid_custom_token(self):
113 response = self.client.get(
114 '/protected', headers={'X-Token': 'this-is-not-the-token!'})
115 self.assertEqual(response.status_code, 401)
116 self.assertTrue('WWW-Authenticate' in response.headers)
117 self.assertEqual(response.headers['WWW-Authenticate'],
118 'Bearer realm="Authentication Required"')
119
120 def test_multi_auth_login_invalid_scheme(self):
121 response = self.client.get(
122 '/protected', headers={'Authorization': 'Foo this-is-the-token!'})
123 self.assertEqual(response.status_code, 401)
124 self.assertTrue('WWW-Authenticate' in response.headers)
125 self.assertEqual(response.headers['WWW-Authenticate'],
126 'Basic realm="Authentication Required"')
127
128 def test_multi_malformed_header(self):
129 response = self.client.get(
130 '/protected', headers={'Authorization': 'token-without-scheme'})
131 self.assertEqual(response.status_code, 401)
132
133 def test_multi_auth_login_valid_basic_role(self):
134 creds = base64.b64encode(b'john:hello').decode('utf-8')
135 response = self.client.get(
136 '/protected-with-role', headers={'Authorization':
137 'Basic ' + creds})
138 self.assertEqual(response.data.decode('utf-8'), 'role access granted')
139
140 def test_multi_auth_login_valid_token_role(self):
141 response = self.client.get(
142 '/protected-with-role', headers={'Authorization':
143 'MyToken this-is-the-token!'})
144 self.assertEqual(response.data.decode('utf-8'), 'role access granted')
145
146 def test_multi_auth_login_valid_custom_token_role(self):
147 response = self.client.get(
148 '/protected-with-role', headers={'X-Token':
149 'this-is-the-custom-token!'})
150 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 sys
1 import unittest
2 import base64
3 from flask import Flask, g
4 from flask_httpauth import HTTPBasicAuth
5 import pytest
6
7
8 @pytest.mark.skipif(sys.version_info < (3, 7), reason='requires python3.7')
9 class HTTPAuthTestCase(unittest.TestCase):
10 def setUp(self):
11 app = Flask(__name__)
12 app.config['SECRET_KEY'] = 'my secret'
13
14 roles_auth = HTTPBasicAuth()
15
16 @roles_auth.verify_password
17 async def roles_auth_verify_password(username, password):
18 g.anon = False
19 if username == 'john':
20 return password == 'hello'
21 elif username == 'susan':
22 return password == 'bye'
23 elif username == 'cindy':
24 return password == 'byebye'
25 elif username == '':
26 g.anon = True
27 return True
28 return False
29
30 @roles_auth.get_user_roles
31 async def get_user_roles(auth):
32 username = auth.username
33 if username == 'john':
34 return 'normal'
35 elif username == 'susan':
36 return ('normal', 'special')
37 elif username == 'cindy':
38 return None
39
40 @roles_auth.error_handler
41 async def error_handler():
42 return 'error', 403 # use a custom error status
43
44 @app.route('/')
45 async def index():
46 return 'index'
47
48 @app.route('/normal')
49 @roles_auth.login_required(role='normal')
50 async def roles_auth_route_normal():
51 return 'normal:' + roles_auth.username()
52
53 @app.route('/special')
54 @roles_auth.login_required(role='special')
55 async def roles_auth_route_special():
56 return 'special:' + roles_auth.username()
57
58 @app.route('/normal-or-special')
59 @roles_auth.login_required(role=('normal', 'special'))
60 async def roles_auth_route_normal_or_special():
61 return 'normal_or_special:' + roles_auth.username()
62
63 @app.route('/normal-and-special')
64 @roles_auth.login_required(role=(('normal', 'special'),))
65 async def roles_auth_route_normal_and_special():
66 return 'normal_and_special:' + roles_auth.username()
67
68 self.app = app
69 self.roles_auth = roles_auth
70 self.client = app.test_client()
71
72 def test_verify_roles_valid_normal_1(self):
73 creds = base64.b64encode(b'susan:bye').decode('utf-8')
74 response = self.client.get(
75 '/normal', headers={'Authorization': 'Basic ' + creds})
76 self.assertEqual(response.data, b'normal:susan')
77
78 def test_verify_roles_valid_normal_2(self):
79 creds = base64.b64encode(b'john:hello').decode('utf-8')
80 response = self.client.get(
81 '/normal', headers={'Authorization': 'Basic ' + creds})
82 self.assertEqual(response.data, b'normal:john')
83
84 def test_verify_auth_login_valid_special(self):
85 creds = base64.b64encode(b'susan:bye').decode('utf-8')
86 response = self.client.get(
87 '/special', headers={'Authorization': 'Basic ' + creds})
88 self.assertEqual(response.data, b'special:susan')
89
90 def test_verify_auth_login_invalid_special_1(self):
91 creds = base64.b64encode(b'john:hello').decode('utf-8')
92 response = self.client.get(
93 '/special', headers={'Authorization': 'Basic ' + creds})
94 self.assertEqual(response.status_code, 403)
95 self.assertTrue('WWW-Authenticate' in response.headers)
96
97 def test_verify_auth_login_invalid_special_2(self):
98 creds = base64.b64encode(b'cindy:byebye').decode('utf-8')
99 response = self.client.get(
100 '/special', headers={'Authorization': 'Basic ' + creds})
101 self.assertEqual(response.status_code, 403)
102 self.assertTrue('WWW-Authenticate' in response.headers)
103
104 def test_verify_auth_login_valid_normal_or_special_1(self):
105 creds = base64.b64encode(b'susan:bye').decode('utf-8')
106 response = self.client.get(
107 '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
108 self.assertEqual(response.data, b'normal_or_special:susan')
109
110 def test_verify_auth_login_valid_normal_or_special_2(self):
111 creds = base64.b64encode(b'john:hello').decode('utf-8')
112 response = self.client.get(
113 '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
114 self.assertEqual(response.data, b'normal_or_special:john')
115
116 def test_verify_auth_login_valid_normal_and_special_1(self):
117 creds = base64.b64encode(b'susan:bye').decode('utf-8')
118 response = self.client.get(
119 '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
120 self.assertEqual(response.data, b'normal_and_special:susan')
121
122 def test_verify_auth_login_valid_normal_and_special_2(self):
123 creds = base64.b64encode(b'john:hello').decode('utf-8')
124 response = self.client.get(
125 '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
126 self.assertEqual(response.status_code, 403)
127 self.assertTrue('WWW-Authenticate' in response.headers)
128
129 def test_verify_auth_login_invalid_password(self):
130 creds = base64.b64encode(b'john:bye').decode('utf-8')
131 response = self.client.get(
132 '/normal', headers={'Authorization': 'Basic ' + creds})
133 self.assertEqual(response.status_code, 403)
134 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')
0 import base64
1 import sys
2 import unittest
3 from flask import Flask
4 from flask_httpauth import HTTPTokenAuth
5 import pytest
6
7
8 @pytest.mark.skipif(sys.version_info < (3, 7), reason='requires python3.7')
9 class HTTPAuthTestCase(unittest.TestCase):
10 def setUp(self):
11 app = Flask(__name__)
12 app.config['SECRET_KEY'] = 'my secret'
13
14 token_auth = HTTPTokenAuth('MyToken')
15 token_auth2 = HTTPTokenAuth('Token', realm='foo')
16 token_auth3 = HTTPTokenAuth(header='X-API-Key')
17
18 @token_auth.verify_token
19 async def verify_token(token):
20 if token == 'this-is-the-token!':
21 return 'user'
22
23 @token_auth3.verify_token
24 async def verify_token3(token):
25 if token == 'this-is-the-token!':
26 return 'user'
27
28 @token_auth.error_handler
29 async def error_handler():
30 return 'error', 401, {'WWW-Authenticate': 'MyToken realm="Foo"'}
31
32 @app.route('/')
33 async def index():
34 return 'index'
35
36 @app.route('/protected')
37 @token_auth.login_required
38 async def token_auth_route():
39 return 'token_auth:' + token_auth.current_user()
40
41 @app.route('/protected-optional')
42 @token_auth.login_required(optional=True)
43 async def token_auth_optional_route():
44 return 'token_auth:' + str(token_auth.current_user())
45
46 @app.route('/protected2')
47 @token_auth2.login_required
48 async def token_auth_route2():
49 return 'token_auth2'
50
51 @app.route('/protected3')
52 @token_auth3.login_required
53 async def token_auth_route3():
54 return 'token_auth3:' + token_auth3.current_user()
55
56 self.app = app
57 self.token_auth = token_auth
58 self.client = app.test_client()
59
60 def test_token_auth_prompt(self):
61 response = self.client.get('/protected')
62 self.assertEqual(response.status_code, 401)
63 self.assertTrue('WWW-Authenticate' in response.headers)
64 self.assertEqual(response.headers['WWW-Authenticate'],
65 'MyToken realm="Foo"')
66
67 def test_token_auth_ignore_options(self):
68 response = self.client.options('/protected')
69 self.assertEqual(response.status_code, 200)
70 self.assertTrue('WWW-Authenticate' not in response.headers)
71
72 def test_token_auth_login_valid(self):
73 response = self.client.get(
74 '/protected', headers={'Authorization':
75 'MyToken this-is-the-token!'})
76 self.assertEqual(response.data.decode('utf-8'), 'token_auth:user')
77
78 def test_token_auth_login_valid_different_case(self):
79 response = self.client.get(
80 '/protected', headers={'Authorization':
81 'mytoken this-is-the-token!'})
82 self.assertEqual(response.data.decode('utf-8'), 'token_auth:user')
83
84 def test_token_auth_login_optional(self):
85 response = self.client.get('/protected-optional')
86 self.assertEqual(response.data.decode('utf-8'), 'token_auth:None')
87
88 def test_token_auth_login_invalid_token(self):
89 response = self.client.get(
90 '/protected', headers={'Authorization':
91 'MyToken this-is-not-the-token!'})
92 self.assertEqual(response.status_code, 401)
93 self.assertTrue('WWW-Authenticate' in response.headers)
94 self.assertEqual(response.headers['WWW-Authenticate'],
95 'MyToken realm="Foo"')
96
97 def test_token_auth_login_invalid_scheme(self):
98 response = self.client.get(
99 '/protected', headers={'Authorization': 'Foo this-is-the-token!'})
100 self.assertEqual(response.status_code, 401)
101 self.assertTrue('WWW-Authenticate' in response.headers)
102 self.assertEqual(response.headers['WWW-Authenticate'],
103 'MyToken realm="Foo"')
104
105 def test_token_auth_login_invalid_header(self):
106 response = self.client.get(
107 '/protected', headers={'Authorization': 'this-is-a-bad-header'})
108 self.assertEqual(response.status_code, 401)
109 self.assertTrue('WWW-Authenticate' in response.headers)
110 self.assertEqual(response.headers['WWW-Authenticate'],
111 'MyToken realm="Foo"')
112
113 def test_token_auth_login_invalid_no_callback(self):
114 response = self.client.get(
115 '/protected2', headers={'Authorization':
116 'Token this-is-the-token!'})
117 self.assertEqual(response.status_code, 401)
118 self.assertTrue('WWW-Authenticate' in response.headers)
119 self.assertEqual(response.headers['WWW-Authenticate'],
120 'Token realm="foo"')
121
122 def test_token_auth_custom_header_valid_token(self):
123 response = self.client.get(
124 '/protected3', headers={'X-API-Key': 'this-is-the-token!'})
125 self.assertEqual(response.status_code, 200)
126 self.assertEqual(response.data.decode('utf-8'), 'token_auth3:user')
127
128 def test_token_auth_custom_header_invalid_token(self):
129 response = self.client.get(
130 '/protected3', headers={'X-API-Key': 'invalid-token-should-fail'})
131 self.assertEqual(response.status_code, 401)
132 self.assertTrue('WWW-Authenticate' in response.headers)
133
134 def test_token_auth_custom_header_invalid_header(self):
135 response = self.client.get(
136 '/protected3', headers={'API-Key': 'this-is-the-token!'})
137 self.assertEqual(response.status_code, 401)
138 self.assertTrue('WWW-Authenticate' in response.headers)
139 self.assertEqual(response.headers['WWW-Authenticate'],
140 'Bearer realm="Authentication Required"')
141
142 def test_token_auth_header_precedence(self):
143 basic_creds = base64.b64encode(b'susan:bye').decode('utf-8')
144 response = self.client.get(
145 '/protected3', headers={'Authorization': 'Basic ' + basic_creds,
146 'X-API-Key': 'this-is-the-token!'})
147 self.assertEqual(response.status_code, 200)
148 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
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=
6 coverage run --branch --include=flask_httpauth.py setup.py test
7 coverage report --show-missing
8 coverage erase
16 pip install -e .
17 pytest -p no:logging --cov=src --cov-branch --cov-report=term-missing
918 deps=
10 coverage
19 asgiref
20 pytest
21 pytest-cov
1122
1223 [testenv:flake8]
13 basepython=python
1424 deps=
1525 flake8
1626 commands=
17 flake8 --exclude=".*" --ignore=E402 flask_httpauth.py tests examples
18
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
27 flake8 --exclude=".*" --ignore=E402 src/flask_httpauth.py tests examples
3628
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