Skip to content

Commit cb988e1

Browse files
Crougvimalloc
authored andcommitted
Make header reading compliant with RFC7230, section 3.2.2 (#270)
* Make header reading compliant with RFC7230, section 3.2.2 * Make header reading compliant with RFC7230, section 3.2.2 * Only attempt to parse comma delimited headers if header_type specified * Add unit tests for multi field headers * Remove redundant checks * Fix ambiguity between header fields beginning with same characters * Fix pep8 errors * Undo unintentional change * Add newline to end of file * Fix more pep8 issues
1 parent 391de47 commit cb988e1

File tree

2 files changed

+77
-8
lines changed

2 files changed

+77
-8
lines changed

flask_jwt_extended/view_decorators.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from functools import wraps
22
from datetime import datetime
33
from calendar import timegm
4+
from re import split
45

56
from werkzeug.exceptions import BadRequest
67

@@ -170,25 +171,36 @@ def _decode_jwt_from_headers():
170171
header_type = config.header_type
171172

172173
# Verify we have the auth header
173-
jwt_header = request.headers.get(header_name, None)
174-
if not jwt_header:
174+
auth_header = request.headers.get(header_name, None)
175+
if not auth_header:
175176
raise NoAuthorizationError("Missing {} Header".format(header_name))
176177

177178
# Make sure the header is in a valid format that we are expecting, ie
178179
# <HeaderName>: <HeaderType(optional)> <JWT>
180+
jwt_header = None
181+
182+
# Check if header is comma delimited, ie
183+
# <HeaderName>: <field> <value>, <field> <value>, etc...
184+
if header_type:
185+
field_values = split(r',\s*', auth_header)
186+
jwt_header = [s for s in field_values if s.split()[0] == header_type]
187+
if len(jwt_header) < 1:
188+
msg = "Bad {} header. Expected value '{} <JWT>'".format(
189+
header_name,
190+
header_type
191+
)
192+
raise InvalidHeaderError(msg)
193+
jwt_header = jwt_header[0]
194+
else:
195+
jwt_header = auth_header
196+
179197
parts = jwt_header.split()
180198
if not header_type:
181199
if len(parts) != 1:
182200
msg = "Bad {} header. Expected value '<JWT>'".format(header_name)
183201
raise InvalidHeaderError(msg)
184202
encoded_token = parts[0]
185203
else:
186-
if parts[0] != header_type or len(parts) != 2:
187-
msg = "Bad {} header. Expected value '{} <JWT>'".format(
188-
header_name,
189-
header_type
190-
)
191-
raise InvalidHeaderError(msg)
192204
encoded_token = parts[1]
193205

194206
return encoded_token, None

tests/test_headers.py

+57
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,39 @@ def access_protected():
1919
return app
2020

2121

22+
def test_default_headers(app):
23+
app.config
24+
test_client = app.test_client()
25+
26+
with app.test_request_context():
27+
access_token = create_access_token('username')
28+
29+
# Ensure other authorization types don't work
30+
access_headers = {'Authorization': 'Basic basiccreds'}
31+
response = test_client.get('/protected', headers=access_headers)
32+
expected_json = {'msg': "Bad Authorization header. Expected value 'Bearer <JWT>'"}
33+
assert response.status_code == 422
34+
assert response.get_json() == expected_json
35+
36+
# Ensure default headers work
37+
access_headers = {'Authorization': 'Bearer {}'.format(access_token)}
38+
response = test_client.get('/protected', headers=access_headers)
39+
assert response.status_code == 200
40+
assert response.get_json() == {'foo': 'bar'}
41+
42+
# Ensure default headers work with multiple field values
43+
access_headers = {'Authorization': 'Bearer {}, Basic creds'.format(access_token)}
44+
response = test_client.get('/protected', headers=access_headers)
45+
assert response.status_code == 200
46+
assert response.get_json() == {'foo': 'bar'}
47+
48+
# Ensure default headers work with multiple field values in any position
49+
access_headers = {'Authorization': 'Basic creds, Bearer {}'.format(access_token)}
50+
response = test_client.get('/protected', headers=access_headers)
51+
assert response.status_code == 200
52+
assert response.get_json() == {'foo': 'bar'}
53+
54+
2255
def test_custom_header_name(app):
2356
app.config['JWT_HEADER_NAME'] = 'Foo'
2457
test_client = app.test_client()
@@ -38,6 +71,18 @@ def test_custom_header_name(app):
3871
assert response.status_code == 200
3972
assert response.get_json() == {'foo': 'bar'}
4073

74+
# Ensure new headers work with multiple field values
75+
access_headers = {'Foo': 'Bearer {}, Basic randomcredshere'.format(access_token)}
76+
response = test_client.get('/protected', headers=access_headers)
77+
assert response.status_code == 200
78+
assert response.get_json() == {'foo': 'bar'}
79+
80+
# Ensure new headers work with multiple field values in any position
81+
access_headers = {'Foo': 'Basic randomcredshere, Bearer {}'.format(access_token)}
82+
response = test_client.get('/protected', headers=access_headers)
83+
assert response.status_code == 200
84+
assert response.get_json() == {'foo': 'bar'}
85+
4186

4287
def test_custom_header_type(app):
4388
app.config['JWT_HEADER_TYPE'] = 'JWT'
@@ -59,6 +104,18 @@ def test_custom_header_type(app):
59104
assert response.status_code == 200
60105
assert response.get_json() == {'foo': 'bar'}
61106

107+
# Ensure new headers work with multiple field values
108+
access_headers = {'Authorization': 'JWT {}, Basic creds'.format(access_token)}
109+
response = test_client.get('/protected', headers=access_headers)
110+
assert response.status_code == 200
111+
assert response.get_json() == {'foo': 'bar'}
112+
113+
# Ensure new headers work with multiple field values in any position
114+
access_headers = {'Authorization': 'Basic creds, JWT {}'.format(access_token)}
115+
response = test_client.get('/protected', headers=access_headers)
116+
assert response.status_code == 200
117+
assert response.get_json() == {'foo': 'bar'}
118+
62119
# Insure new headers without a type also work
63120
app.config['JWT_HEADER_TYPE'] = ''
64121
access_headers = {'Authorization': access_token}

0 commit comments

Comments
 (0)