New upstream version 3.2.4
MartÃn Ferrari
5 years ago
0 | 0 | language: python |
1 | env: | |
2 | - TOXENV=flake8 | |
3 | - TOXENV=py26 | |
4 | - TOXENV=py27 | |
5 | - TOXENV=py33 | |
6 | - TOXENV=py34 | |
7 | - TOXENV=py35 | |
8 | - TOXENV=pypy | |
9 | - TOXENV=docs | |
1 | matrix: | |
2 | include: | |
3 | - python: 3.6 | |
4 | env: TOXENV=flake8 | |
5 | - python: 2.7 | |
6 | env: TOXENV=py27 | |
7 | - python: 3.4 | |
8 | env: TOXENV=py34 | |
9 | - python: 3.5 | |
10 | env: TOXENV=py35 | |
11 | - python: 3.6 | |
12 | env: TOXENV=py36 | |
13 | - python: pypy | |
14 | env: TOXENV=pypy | |
15 | - python: pypy3 | |
16 | env: TOXENV=pypy3 | |
17 | - python: 3.6 | |
18 | env: TOXENV=docs | |
10 | 19 | install: |
11 | 20 | - pip install tox |
12 | 21 | script: |
0 | 0 | # Flask-HTTPAuth Change Log |
1 | 1 | |
2 | ## Unreleased | |
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)) | |
3 | 15 | |
4 | 16 | ## Release 3.1.2 - 2016-04-20 |
5 | 17 |
77 | 77 | Resources |
78 | 78 | --------- |
79 | 79 | |
80 | - [Documentation](http://pythonhosted.org/Flask-HTTPAuth) | |
81 | - [pypi](https://pypi.python.org/pypi/Flask-HTTPAuth) | |
80 | - [Documentation](http://flask-httpauth.readthedocs.io/en/latest/) | |
81 | - [PyPI](https://pypi.org/project/Flask-HTTPAuth) | |
82 | 82 | - [Change log](https://github.com/miguelgrinberg/Flask-HTTPAuth/blob/master/CHANGELOG.md) |
51 | 51 | |
52 | 52 | @auth.hash_password |
53 | 53 | def hash_pw(username, password): |
54 | get_salt(username) | |
54 | salt = get_salt(username) | |
55 | 55 | return hash(password, salt) |
56 | 56 | |
57 | 57 | For the most degree of flexibility the `get_password` and `hash_password` callbacks can be replaced with `verify_password`:: |
96 | 96 | Security Concerns with Digest Authentication |
97 | 97 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
98 | 98 | |
99 | The digest authentication algorihtm requires a *challenge* to be sent to the client for use in encrypting the password for transmission. This challenge needs to be used again when the password is decoded at the server, so the challenge information needs to be stored so that it can be recalled later. | |
99 | The digest authentication algorithm requires a *challenge* to be sent to the client for use in encrypting the password for transmission. This challenge needs to be used again when the password is decoded at the server, so the challenge information needs to be stored so that it can be recalled later. | |
100 | 100 | |
101 | 101 | By default, Flask-HTTPAuth stores the challenge data in the Flask session. To make the authentication flow secure when using session storage, it is required that server-side sessions are used instead of the default Flask cookie based sessions, as this ensures that the challenge data is not at risk of being captured as it moves in a cookie between server and client. The Flask-Session and Flask-KVSession extensions are both very good options to implement server-side sessions. |
102 | 102 | |
166 | 166 | Using Multiple Authentication Schemes |
167 | 167 | ------------------------------------- |
168 | 168 | |
169 | Applications sometimes need to support a combination of authentication methods. For example, a web application could be authenticating 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. | |
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. | |
170 | 170 | |
171 | 171 | In the examples directory you can find a complete example that uses basic and token authentication. |
172 | 172 | |
212 | 212 | |
213 | 213 | @auth.hash_password |
214 | 214 | def hash_pw(username, password): |
215 | get_salt(username) | |
215 | salt = get_salt(username) | |
216 | 216 | return hash(password, salt) |
217 | 217 | |
218 | 218 | .. method:: verify_password(verify_password_callback) |
240 | 240 | |
241 | 241 | .. method:: login_required(view_function_callback) |
242 | 242 | |
243 | This callback function will be called when authentication is succesful. This will typically be a Flask view function. Example:: | |
243 | This callback function will be called when authentication is successful. This will typically be a Flask view function. Example:: | |
244 | 244 | |
245 | 245 | @app.route('/private') |
246 | 246 | @auth.login_required |
46 | 46 | g.user = None |
47 | 47 | try: |
48 | 48 | data = jwt.loads(token) |
49 | except: | |
49 | except: # noqa: E722 | |
50 | 50 | return False |
51 | 51 | if 'username' in data: |
52 | 52 | g.user = data['username'] |
1 | 1 | """Token authentication example |
2 | 2 | |
3 | 3 | This example demonstrates how to protect Flask endpoints with token |
4 | authentication, using JWT tokens. | |
4 | authentication, using tokens. | |
5 | 5 | |
6 | 6 | When this application starts, a token is generated for each of the two users. |
7 | 7 | To gain access, you can use a command line HTTP client such as curl, passing |
9 | 9 | |
10 | 10 | curl -X GET -H "Authorization: Bearer <jwt-token>" http://localhost:5000/ |
11 | 11 | |
12 | The response should include the username, which is obtained from the JWT token. | |
12 | The response should include the username, which is obtained from the token. | |
13 | 13 | """ |
14 | 14 | from flask import Flask, g |
15 | 15 | from flask_httpauth import HTTPTokenAuth |
16 | from itsdangerous import TimedJSONWebSignatureSerializer as JWT | |
16 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer | |
17 | 17 | |
18 | 18 | |
19 | 19 | app = Flask(__name__) |
20 | 20 | app.config['SECRET_KEY'] = 'top secret!' |
21 | jwt = JWT(app.config['SECRET_KEY'], expires_in=3600) | |
21 | token_serializer = Serializer(app.config['SECRET_KEY'], expires_in=3600) | |
22 | 22 | |
23 | 23 | auth = HTTPTokenAuth('Bearer') |
24 | 24 | |
25 | 25 | |
26 | 26 | users = ['john', 'susan'] |
27 | 27 | for user in users: |
28 | token = jwt.dumps({'username': user}) | |
28 | token = token_serializer.dumps({'username': user}).decode('utf-8') | |
29 | 29 | print('*** token for {}: {}\n'.format(user, token)) |
30 | 30 | |
31 | 31 | |
33 | 33 | def verify_token(token): |
34 | 34 | g.user = None |
35 | 35 | try: |
36 | data = jwt.loads(token) | |
37 | except: | |
36 | data = token_serializer.loads(token) | |
37 | except: # noqa: E722 | |
38 | 38 | return False |
39 | 39 | if 'username' in data: |
40 | 40 | g.user = data['username'] |
13 | 13 | from flask import request, make_response, session |
14 | 14 | from werkzeug.datastructures import Authorization |
15 | 15 | |
16 | __version__ = '3.2.1' | |
16 | __version__ = '3.2.4' | |
17 | 17 | |
18 | 18 | |
19 | 19 | class HTTPAuth(object): |
53 | 53 | def authenticate_header(self): |
54 | 54 | return '{0} realm="{1}"'.format(self.scheme, self.realm) |
55 | 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 | ||
56 | 86 | def login_required(self, f): |
57 | 87 | @wraps(f) |
58 | 88 | def decorated(*args, **kwargs): |
59 | auth = request.authorization | |
60 | if auth is None and 'Authorization' in request.headers: | |
61 | # Flask/Werkzeug do not recognize any authentication types | |
62 | # other than Basic or Digest, so here we parse the header by | |
63 | # hand | |
64 | try: | |
65 | auth_type, token = request.headers['Authorization'].split( | |
66 | None, 1) | |
67 | auth = Authorization(auth_type, {'token': token}) | |
68 | except ValueError: | |
69 | # The Authorization header is either empty or has no token | |
70 | pass | |
71 | ||
72 | # if the auth type does not match, we act as if there is no auth | |
73 | # this is better than failing directly, as it allows the callback | |
74 | # to handle special cases, like supporting multiple auth types | |
75 | if auth is not None and auth.type.lower() != self.scheme.lower(): | |
76 | auth = None | |
89 | auth = self.get_auth() | |
77 | 90 | |
78 | 91 | # Flask normally handles OPTIONS requests on its own, but in the |
79 | 92 | # case it is configured to forward those to the application, we |
80 | 93 | # need to ignore authentication headers and let the request through |
81 | 94 | # to avoid unwanted interactions with CORS. |
82 | 95 | if request.method != 'OPTIONS': # pragma: no cover |
83 | if auth and auth.username: | |
84 | password = self.get_password_callback(auth.username) | |
85 | else: | |
86 | password = None | |
96 | password = self.get_auth_password(auth) | |
97 | ||
87 | 98 | if not self.authenticate(auth, password): |
88 | 99 | # Clear TCP receive buffer of any pending data |
89 | 100 | request.data |
256 | 267 | def decorated(*args, **kwargs): |
257 | 268 | selected_auth = None |
258 | 269 | if 'Authorization' in request.headers: |
259 | scheme, creds = request.headers['Authorization'].split(None, 1) | |
260 | for auth in self.additional_auth: | |
261 | if auth.scheme == scheme: | |
262 | selected_auth = auth | |
263 | break | |
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 | |
264 | 281 | if selected_auth is None: |
265 | 282 | selected_auth = self.main_auth |
266 | 283 | return selected_auth.login_required(f)(*args, **kwargs) |
80 | 80 | self.assertTrue('WWW-Authenticate' in response.headers) |
81 | 81 | self.assertEqual(response.headers['WWW-Authenticate'], |
82 | 82 | 'Basic realm="Authentication Required"') |
83 | ||
84 | def test_multi_malformed_header(self): | |
85 | response = self.client.get( | |
86 | '/protected', headers={'Authorization': 'token-without-scheme'}) | |
87 | self.assertEqual(response.status_code, 401) |
0 | 0 | [tox] |
1 | envlist=flake8,py27,py33,py34,py35,pypy,docs,coverage | |
1 | envlist=flake8,py27,py34,py35,py36,pypy,docs,coverage | |
2 | 2 | skip_missing_interpreters=True |
3 | 3 | |
4 | 4 | [testenv] |
6 | 6 | coverage run --branch --include=flask_httpauth.py setup.py test |
7 | 7 | coverage report --show-missing |
8 | 8 | coverage erase |
9 | deps= | |
10 | coverage | |
9 | 11 | |
10 | 12 | [testenv:flake8] |
11 | 13 | basepython=python |
16 | 18 | |
17 | 19 | [testenv:py26] |
18 | 20 | basepython=python2.6 |
19 | deps= | |
20 | coverage | |
21 | 21 | |
22 | 22 | [testenv:py27] |
23 | 23 | basepython=python2.7 |
24 | deps= | |
25 | coverage | |
26 | ||
27 | [testenv:py33] | |
28 | basepython=python3.3 | |
29 | deps= | |
30 | coverage | |
31 | 24 | |
32 | 25 | [testenv:py34] |
33 | 26 | basepython=python3.4 |
34 | deps= | |
35 | coverage | |
36 | 27 | |
37 | 28 | [testenv:py35] |
38 | 29 | basepython=python3.5 |
39 | deps= | |
40 | coverage | |
30 | ||
31 | [testenv:py36] | |
32 | basepython=python3.6 | |
41 | 33 | |
42 | 34 | [testenv:pypy] |
43 | 35 | basepython=pypy |
44 | deps= | |
45 | coverage | |
46 | 36 | |
47 | 37 | [testenv:docs] |
48 | 38 | basepython=python2.7 |
56 | 46 | |
57 | 47 | [testenv:coverage] |
58 | 48 | basepython=python |
59 | deps= | |
60 | coverage | |
61 | 49 | commands= |
62 | 50 | coverage run --branch --source=flask_httpauth.py setup.py test |
63 | 51 | coverage html |