New upstream release.
Debian Janitor
11 months ago
21 | 21 | strategy: |
22 | 22 | matrix: |
23 | 23 | os: [ubuntu-latest, macos-latest, windows-latest] |
24 | python: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.8'] | |
24 | python: ['3.8', '3.9', '3.10', '3.11', 'pypy-3.8'] | |
25 | flask: ['flask<2.3', 'flask>=2.3'] | |
25 | 26 | fail-fast: false |
26 | 27 | runs-on: ${{ matrix.os }} |
27 | 28 | steps: |
32 | 33 | - run: python -m pip install --upgrade pip wheel |
33 | 34 | - run: pip install tox tox-gh-actions |
34 | 35 | - run: tox |
36 | env: | |
37 | FLASK_VERSION: ${{ matrix.flask }} | |
35 | 38 | coverage: |
36 | 39 | name: coverage |
37 | 40 | runs-on: ubuntu-latest |
38 | 41 | steps: |
39 | - uses: actions/checkout@v2 | |
40 | - uses: actions/setup-python@v2 | |
42 | - uses: actions/checkout@v3 | |
43 | - uses: actions/setup-python@v3 | |
41 | 44 | - run: python -m pip install --upgrade pip wheel |
42 | - run: pip install tox tox-gh-actions codecov | |
45 | - run: pip install tox tox-gh-actions | |
43 | 46 | - run: tox |
44 | - run: codecov | |
47 | - uses: codecov/codecov-action@v3 | |
48 | with: | |
49 | files: ./coverage.xml | |
50 | fail_ci_if_error: true |
0 | 0 | # Flask-HTTPAuth change log |
1 | ||
2 | **Release 4.8.0** - 2023-04-27 | |
3 | ||
4 | - Changes to handle breaking changes in Flask/Werkzeug 2.3 [#160](https://github.com/miguelgrinberg/flask-httpauth/issues/160) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/76548398847fcedc8421a8f4b4c2d12ee16270fe)) | |
5 | - Remove Python 3.6 and 3.7 from builds, add Python 3.11 ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/04399bd2e121f8f0bff34360af74978920ba9096)) | |
6 | - Replace itsdangerous with pyjwt in examples [#157](https://github.com/miguelgrinberg/flask-httpauth/issues/157) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/6f708b09f4d54e48ef1a3501c63867ffb1789077)) | |
7 | - Better documentation for the `get_user_roles` callback argument [#152](https://github.com/miguelgrinberg/flask-httpauth/issues/152) ([commit](https://github.com/miguelgrinberg/flask-httpauth/commit/03ff9443114ac57cae34dcf0aae39e89985529b0)) (thanks **Taranjeet Singh**!) | |
1 | 8 | |
2 | 9 | **Release 4.7.0** - 2022-05-29 |
3 | 10 |
0 | python-flask-httpauth (4.7.0-1) UNRELEASED; urgency=low | |
0 | python-flask-httpauth (4.8.0-1) UNRELEASED; urgency=low | |
1 | 1 | |
2 | 2 | * New upstream release. |
3 | * New upstream release. | |
3 | 4 | |
4 | -- Debian Janitor <janitor@jelmer.uk> Sat, 31 Dec 2022 00:20:44 -0000 | |
5 | -- Debian Janitor <janitor@jelmer.uk> Fri, 19 May 2023 23:57:37 -0000 | |
5 | 6 | |
6 | 7 | python-flask-httpauth (4.5.0-4) unstable; urgency=medium |
7 | 8 |
212 | 212 | |
213 | 213 | .. method:: get_user_roles(roles_callback) |
214 | 214 | |
215 | 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:: | |
215 | 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. If the verify callback returned ``True`` instead of a user object, then the ``Authorization`` object provided by Flask will be passed to this function. The function should return the role or list of roles that belong to the user. Example:: | |
216 | 216 | |
217 | 217 | @auth.get_user_roles |
218 | 218 | def get_user_roles(user): |
5 | 5 | |
6 | 6 | The root URL for this application can be accessed via basic auth, providing |
7 | 7 | username and password, or via token auth, providing a bearer JWS token. |
8 | ||
9 | This example requires the PyJWT package to be installed. | |
8 | 10 | """ |
11 | from time import time | |
9 | 12 | from flask import Flask |
10 | 13 | from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth |
11 | 14 | from werkzeug.security import generate_password_hash, check_password_hash |
12 | from itsdangerous import TimedJSONWebSignatureSerializer as JWS | |
15 | import jwt | |
13 | 16 | |
14 | 17 | |
15 | 18 | app = Flask(__name__) |
16 | 19 | app.config['SECRET_KEY'] = 'top secret!' |
17 | jws = JWS(app.config['SECRET_KEY'], expires_in=3600) | |
18 | 20 | |
19 | 21 | basic_auth = HTTPBasicAuth() |
20 | 22 | token_auth = HTTPTokenAuth('Bearer') |
27 | 29 | } |
28 | 30 | |
29 | 31 | for user in users.keys(): |
30 | token = jws.dumps({'username': user}) | |
32 | token = jwt.encode({'username': user, 'exp': int(time()) + 3600}, | |
33 | app.config['SECRET_KEY'], algorithm='HS256') | |
31 | 34 | print('*** token for {}: {}\n'.format(user, token)) |
32 | 35 | |
33 | 36 | |
41 | 44 | @token_auth.verify_token |
42 | 45 | def verify_token(token): |
43 | 46 | try: |
44 | data = jws.loads(token) | |
47 | data = jwt.decode(token, app.config['SECRET_KEY'], | |
48 | algorithms=['HS256']) | |
45 | 49 | except: # noqa: E722 |
46 | 50 | return False |
47 | 51 | if 'username' in data: |
1 | 1 | """Token authentication example |
2 | 2 | |
3 | 3 | This example demonstrates how to protect Flask endpoints with token |
4 | authentication, using tokens. | |
4 | authentication, using JWT tokens. To use this example you need to install the | |
5 | PyJWT library: | |
6 | ||
7 | pip install pyjwt | |
5 | 8 | |
6 | 9 | When this application starts, a token is generated for each of the two users. |
7 | 10 | To gain access, you can use a command line HTTP client such as curl, passing |
9 | 12 | |
10 | 13 | curl -X GET -H "Authorization: Bearer <jws-token>" http://localhost:5000/ |
11 | 14 | |
12 | The response should include the username, which is obtained from the token. | |
15 | The response should include the username, which is obtained from the token. The | |
16 | tokens have a validity time of one hour, after which they will be rejected. | |
13 | 17 | """ |
18 | from time import time | |
14 | 19 | from flask import Flask |
15 | 20 | from flask_httpauth import HTTPTokenAuth |
16 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer | |
21 | import jwt | |
17 | 22 | |
18 | 23 | |
19 | 24 | app = Flask(__name__) |
20 | 25 | app.config['SECRET_KEY'] = 'top secret!' |
21 | token_serializer = Serializer(app.config['SECRET_KEY'], expires_in=3600) | |
22 | 26 | |
23 | 27 | auth = HTTPTokenAuth('Bearer') |
24 | 28 | |
25 | 29 | |
26 | 30 | users = ['john', 'susan'] |
27 | 31 | for user in users: |
28 | token = token_serializer.dumps({'username': user}).decode('utf-8') | |
32 | token = jwt.encode({'username': user, 'exp': int(time()) + 3600}, | |
33 | app.config['SECRET_KEY'], algorithm='HS256') | |
29 | 34 | print('*** token for {}: {}\n'.format(user, token)) |
30 | 35 | |
31 | 36 | |
32 | 37 | @auth.verify_token |
33 | 38 | def verify_token(token): |
34 | 39 | try: |
35 | data = token_serializer.loads(token) | |
40 | data = jwt.decode(token, app.config['SECRET_KEY'], | |
41 | algorithms=['HS256']) | |
36 | 42 | except: # noqa: E722 |
37 | 43 | return False |
38 | 44 | if 'username' in data: |
0 | 0 | [metadata] |
1 | 1 | name = Flask-HTTPAuth |
2 | version = 4.7.0 | |
2 | version = 4.8.0 | |
3 | 3 | author = Miguel Grinberg |
4 | 4 | author_email = miguel.grinberg@gmail.com |
5 | 5 | description = HTTP authentication for Flask routes |
75 | 75 | auth = None |
76 | 76 | if self.header is None or self.header == 'Authorization': |
77 | 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 | |
78 | if auth is None and \ | |
79 | 'Authorization' in request.headers: # pragma: no cover | |
80 | # Flask/Werkzeug versions before 2.3 do not recognize any | |
81 | # authentication types other than Basic or Digest, so here we | |
82 | # parse the header by hand | |
82 | 83 | try: |
83 | 84 | auth_type, token = request.headers['Authorization'].split( |
84 | 85 | None, 1) |
85 | auth = Authorization(auth_type, {'token': token}) | |
86 | auth = Authorization(auth_type) | |
87 | auth.token = token | |
86 | 88 | except (ValueError, KeyError): |
87 | 89 | # The Authorization header is either empty or has no token |
88 | 90 | pass |
89 | 91 | elif self.header in request.headers: |
90 | 92 | # using a custom header, so the entire value of the header is |
91 | 93 | # assumed to be a token |
92 | auth = Authorization(self.scheme, | |
93 | {'token': request.headers[self.header]}) | |
94 | auth = Authorization(self.scheme) | |
95 | auth.token = request.headers[self.header] | |
94 | 96 | |
95 | 97 | # if the auth type does not match, we act as if there is no auth |
96 | 98 | # this is better than failing directly, as it allows the callback |
354 | 356 | or not auth.nonce or not auth.response \ |
355 | 357 | or not stored_password_or_ha1: |
356 | 358 | return False |
357 | if not(self.verify_nonce_callback(auth.nonce)) or \ | |
358 | not(self.verify_opaque_callback(auth.opaque)): | |
359 | if not self.verify_nonce_callback(auth.nonce) or \ | |
360 | not self.verify_opaque_callback(auth.opaque): | |
359 | 361 | return False |
360 | 362 | if auth.qop and auth.qop not in self.qop: # pragma: no cover |
361 | 363 | return False |
390 | 392 | return f |
391 | 393 | |
392 | 394 | def authenticate(self, auth, stored_password): |
393 | if auth: | |
394 | token = auth['token'] | |
395 | else: | |
396 | token = "" | |
395 | token = getattr(auth, 'token', '') | |
397 | 396 | if self.verify_token_callback: |
398 | 397 | return self.ensure_sync(self.verify_token_callback)(token) |
399 | 398 |
29 | 29 | |
30 | 30 | @token_auth.get_user_roles |
31 | 31 | def get_token_role(auth): |
32 | if auth['token'] == 'this-is-the-token!': | |
32 | if auth.token == 'this-is-the-token!': | |
33 | 33 | return 'foo' |
34 | 34 | return |
35 | 35 | |
43 | 43 | |
44 | 44 | @custom_token_auth.get_user_roles |
45 | 45 | def get_custom_token_role(auth): |
46 | if auth['token'] == 'this-is-the-custom-token!': | |
46 | if auth.token == 'this-is-the-custom-token!': | |
47 | 47 | return 'foo' |
48 | 48 | return |
49 | 49 |
32 | 32 | |
33 | 33 | @token_auth.get_user_roles |
34 | 34 | async def get_token_role(auth): |
35 | if auth['token'] == 'this-is-the-token!': | |
35 | if auth.token == 'this-is-the-token!': | |
36 | 36 | return 'foo' |
37 | 37 | return |
38 | 38 | |
46 | 46 | |
47 | 47 | @custom_token_auth.get_user_roles |
48 | 48 | async def get_custom_token_role(auth): |
49 | if auth['token'] == 'this-is-the-custom-token!': | |
49 | if auth.token == 'this-is-the-custom-token!': | |
50 | 50 | return 'foo' |
51 | 51 | return |
52 | 52 |
0 | 0 | [tox] |
1 | envlist=flake8,py36,py37,py38,py39,py310pypy3,docs | |
1 | envlist=flake8,py37,py38,py39,py310,py311,pypy3,docs | |
2 | 2 | skip_missing_interpreters=True |
3 | 3 | |
4 | 4 | [gh-actions] |
5 | 5 | python = |
6 | 3.6: py36 | |
7 | 6 | 3.7: py37 |
8 | 7 | 3.8: py38 |
9 | 8 | 3.9: py39 |
10 | 9 | 3.10: py310 |
10 | 3.11: py311 | |
11 | 11 | pypy-3: pypy3 |
12 | 12 | |
13 | 13 | [testenv] |
14 | 14 | commands= |
15 | 15 | pip install -e . |
16 | pytest -p no:logging --cov=src --cov-branch --cov-report=term-missing | |
16 | pip install {env:FLASK_VERSION:flask>=2.3} | |
17 | pytest -p no:logging --cov=src --cov-branch --cov-report=term-missing --cov-report=xml | |
17 | 18 | deps= |
18 | 19 | asgiref |
19 | 20 | pytest |
29 | 30 | changedir=docs |
30 | 31 | deps= |
31 | 32 | sphinx |
32 | whitelist_externals= | |
33 | allowlist_externals= | |
33 | 34 | make |
34 | 35 | commands= |
35 | 36 | make html |