diff --git a/flask_jwt_extended/config.py b/flask_jwt_extended/config.py index 71e05b45..2d50c98f 100644 --- a/flask_jwt_extended/config.py +++ b/flask_jwt_extended/config.py @@ -1,6 +1,12 @@ import datetime from warnings import warn +# In Python 2.7 collections.abc is a part of the collections module. +try: + from collections.abc import Sequence, Set +except ImportError: # pragma: no cover + from collections import Sequence, Set + from flask import current_app # Older versions of pyjwt do not have the requires_cryptography set. Also, @@ -42,9 +48,11 @@ def decode_key(self): @property def token_location(self): locations = current_app.config['JWT_TOKEN_LOCATION'] - if not isinstance(locations, list): - locations = [locations] - if not locations: + if isinstance(locations, str): + locations = (locations,) + elif not isinstance(locations, (Sequence, Set)): + raise RuntimeError('JWT_TOKEN_LOCATION must be a sequence or a set') + elif not locations: raise RuntimeError('JWT_TOKEN_LOCATION must contain at least one ' 'of "headers", "cookies", "query_string", or "json"') for location in locations: @@ -202,8 +210,10 @@ def blacklist_enabled(self): @property def blacklist_checks(self): check_type = current_app.config['JWT_BLACKLIST_TOKEN_CHECKS'] - if not isinstance(check_type, list): - check_type = [check_type] + if isinstance(check_type, str): + check_type = (check_type,) + elif not isinstance(check_type, (Sequence, Set)): + raise RuntimeError('JWT_BLACKLIST_TOKEN_CHECKS must be a sequence or a set') for item in check_type: if item not in ('access', 'refresh'): err = 'JWT_BLACKLIST_TOKEN_CHECKS must be "access" or "refresh"' diff --git a/flask_jwt_extended/jwt_manager.py b/flask_jwt_extended/jwt_manager.py index d60fd05b..496a792d 100644 --- a/flask_jwt_extended/jwt_manager.py +++ b/flask_jwt_extended/jwt_manager.py @@ -138,7 +138,7 @@ def _set_default_configuration_options(app): Sets the default configuration options used by this extension """ # Where to look for the JWT. Available options are cookies or headers - app.config.setdefault('JWT_TOKEN_LOCATION', ['headers']) + app.config.setdefault('JWT_TOKEN_LOCATION', ('headers',)) # Options for JWTs when the TOKEN_LOCATION is headers app.config.setdefault('JWT_HEADER_NAME', 'Authorization') @@ -192,7 +192,7 @@ def _set_default_configuration_options(app): # Options for blacklisting/revoking tokens app.config.setdefault('JWT_BLACKLIST_ENABLED', False) - app.config.setdefault('JWT_BLACKLIST_TOKEN_CHECKS', ['access', 'refresh']) + app.config.setdefault('JWT_BLACKLIST_TOKEN_CHECKS', ('access', 'refresh')) app.config.setdefault('JWT_IDENTITY_CLAIM', 'identity') app.config.setdefault('JWT_USER_CLAIMS', 'user_claims') diff --git a/tests/test_config.py b/tests/test_config.py index 24daeb62..a2b5c04c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,7 +18,7 @@ def app(): def test_default_configs(app): with app.test_request_context(): - assert config.token_location == ['headers'] + assert config.token_location == ('headers',) assert config.jwt_in_query_string is False assert config.jwt_in_cookies is False assert config.jwt_in_json is False @@ -56,7 +56,7 @@ def test_default_configs(app): assert config.algorithm == 'HS256' assert config.is_asymmetric is False assert config.blacklist_enabled is False - assert config.blacklist_checks == ['access', 'refresh'] + assert config.blacklist_checks == ('access', 'refresh') assert config.blacklist_access_tokens is True assert config.blacklist_refresh_tokens is True @@ -105,7 +105,7 @@ def test_override_configs(app): app.config['JWT_ALGORITHM'] = 'HS512' app.config['JWT_BLACKLIST_ENABLED'] = True - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ('refresh',) app.config['JWT_IDENTITY_CLAIM'] = 'foo' app.config['JWT_USER_CLAIMS'] = 'bar' @@ -156,7 +156,7 @@ class CustomJSONEncoder(JSONEncoder): assert config.algorithm == 'HS512' assert config.blacklist_enabled is True - assert config.blacklist_checks == ['refresh'] + assert config.blacklist_checks == ('refresh',) assert config.blacklist_access_tokens is False assert config.blacklist_refresh_tokens is True @@ -246,6 +246,18 @@ def test_invalid_config_options(app): with pytest.raises(RuntimeError): config.token_location + app.config['JWT_TOKEN_LOCATION'] = 1 + with pytest.raises(RuntimeError): + config.token_location + + app.config['JWT_TOKEN_LOCATION'] = {'location': 'headers'} + with pytest.raises(RuntimeError): + config.token_location + + app.config['JWT_TOKEN_LOCATION'] = range(99) + with pytest.raises(RuntimeError): + config.token_location + app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies', 'banana'] with pytest.raises(RuntimeError): config.token_location @@ -275,6 +287,18 @@ def test_invalid_config_options(app): with pytest.raises(RuntimeError): config.blacklist_checks + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 1 + with pytest.raises(RuntimeError): + config.blacklist_checks + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = {'token_type': 'access'} + with pytest.raises(RuntimeError): + config.blacklist_checks + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = range(99) + with pytest.raises(RuntimeError): + config.blacklist_checks + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'banana'] with pytest.raises(RuntimeError): config.blacklist_checks @@ -282,30 +306,54 @@ def test_invalid_config_options(app): def test_jwt_token_locations_config(app): with app.test_request_context(): - app.config['JWT_TOKEN_LOCATION'] = 'headers' - assert config.token_location == ['headers'] - app.config['JWT_TOKEN_LOCATION'] = ['headers'] - assert config.token_location == ['headers'] - app.config['JWT_TOKEN_LOCATION'] = 'cookies' - assert config.token_location == ['cookies'] - app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - assert config.token_location == ['cookies'] - app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] - assert config.token_location == ['cookies', 'headers'] + allowed_locations = ('headers', 'cookies', 'query_string', 'json') + allowed_data_structures = (tuple, list, frozenset, set) + + for location in allowed_locations: + app.config['JWT_TOKEN_LOCATION'] = location + assert config.token_location == (location,) + + for locations in ( + data_structure((location,)) + for data_structure in allowed_data_structures + for location in allowed_locations + ): + app.config['JWT_TOKEN_LOCATION'] = locations + assert config.token_location == locations + + for locations in ( + data_structure(allowed_locations[:i]) + for data_structure in allowed_data_structures + for i in range(1, len(allowed_locations)) + ): + app.config['JWT_TOKEN_LOCATION'] = locations + assert config.token_location == locations def test_jwt_blacklist_token_checks_config(app): with app.test_request_context(): - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'access' - assert config.blacklist_checks == ['access'] - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access'] - assert config.blacklist_checks == ['access'] - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' - assert config.blacklist_checks == ['refresh'] - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['refresh'] - assert config.blacklist_checks == ['refresh'] - app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh'] - assert config.blacklist_checks == ['access', 'refresh'] + allowed_token_types = ('access', 'refresh') + allowed_data_structures = (tuple, list, frozenset, set) + + for token_type in allowed_token_types: + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = token_type + assert config.blacklist_checks == (token_type,) + + for token_types in ( + data_structure((token_type,)) + for data_structure in allowed_data_structures + for token_type in allowed_token_types + ): + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = token_types + assert config.blacklist_checks == token_types + + for token_types in ( + data_structure(allowed_token_types[:i]) + for data_structure in allowed_data_structures + for i in range(1, len(allowed_token_types)) + ): + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = token_types + assert config.blacklist_checks == token_types def test_csrf_protect_config(app):