diff --git a/flask_jwt_extended/__init__.py b/flask_jwt_extended/__init__.py index 4c77bab1..ec10bada 100644 --- a/flask_jwt_extended/__init__.py +++ b/flask_jwt_extended/__init__.py @@ -8,3 +8,5 @@ unset_jwt_cookies, get_raw_jwt, get_current_user, current_user, get_jti, decode_token, get_csrf_token ) + +__version__ = '3.7.0' diff --git a/flask_jwt_extended/jwt_manager.py b/flask_jwt_extended/jwt_manager.py index 6915313e..461b702d 100644 --- a/flask_jwt_extended/jwt_manager.py +++ b/flask_jwt_extended/jwt_manager.py @@ -74,10 +74,6 @@ def init_app(self, app): self._set_default_configuration_options(app) self._set_error_handler_callbacks(app) - # Set propagate exceptions, so all of our error handlers properly - # work in production - app.config['PROPAGATE_EXCEPTIONS'] = True - def _set_error_handler_callbacks(self, app): """ Sets the error handler callbacks used by this extension diff --git a/flask_jwt_extended/utils.py b/flask_jwt_extended/utils.py index 390591b9..feb09999 100644 --- a/flask_jwt_extended/utils.py +++ b/flask_jwt_extended/utils.py @@ -7,6 +7,9 @@ from flask import _request_ctx_stack as ctx_stack from flask_jwt_extended.config import config +from flask_jwt_extended.exceptions import ( + RevokedTokenError, UserClaimsVerificationError, WrongTokenError +) from flask_jwt_extended.tokens import decode_jwt @@ -153,9 +156,31 @@ def token_in_blacklist(*args, **kwargs): return jwt_manager._token_in_blacklist_callback(*args, **kwargs) -def verify_token_claims(*args, **kwargs): +def verify_token_type(decoded_token, expected_type): + if decoded_token['type'] != expected_type: + raise WrongTokenError('Only {} tokens are allowed'.format(expected_type)) + + +def verify_token_not_blacklisted(decoded_token, request_type): + if not config.blacklist_enabled: + return + if not has_token_in_blacklist_callback(): + raise RuntimeError("A token_in_blacklist_callback must be provided via " + "the '@token_in_blacklist_loader' if " + "JWT_BLACKLIST_ENABLED is True") + if config.blacklist_access_tokens and request_type == 'access': + if token_in_blacklist(decoded_token): + raise RevokedTokenError('Token has been revoked') + if config.blacklist_refresh_tokens and request_type == 'refresh': + if token_in_blacklist(decoded_token): + raise RevokedTokenError('Token has been revoked') + + +def verify_token_claims(jwt_data): jwt_manager = _get_jwt_manager() - return jwt_manager._claims_verification_callback(*args, **kwargs) + user_claims = jwt_data[config.user_claims_key] + if not jwt_manager._claims_verification_callback(user_claims): + raise UserClaimsVerificationError('User claims verification failed') def get_csrf_token(encoded_token): diff --git a/flask_jwt_extended/view_decorators.py b/flask_jwt_extended/view_decorators.py index fa86829b..e45333e7 100644 --- a/flask_jwt_extended/view_decorators.py +++ b/flask_jwt_extended/view_decorators.py @@ -10,13 +10,12 @@ from flask_jwt_extended.config import config from flask_jwt_extended.exceptions import ( - InvalidHeaderError, NoAuthorizationError, WrongTokenError, - FreshTokenRequired, CSRFError, UserLoadError, RevokedTokenError, - UserClaimsVerificationError + CSRFError, FreshTokenRequired, InvalidHeaderError, NoAuthorizationError, + UserLoadError ) from flask_jwt_extended.utils import ( - has_user_loader, user_loader, token_in_blacklist, decode_token, - has_token_in_blacklist_callback, verify_token_claims + decode_token, has_user_loader, user_loader, verify_token_claims, + verify_token_not_blacklisted, verify_token_type ) @@ -35,8 +34,7 @@ def wrapper(*args, **kwargs): if request.method not in config.exempt_methods: jwt_data = _decode_jwt_from_request(request_type='access') ctx_stack.top.jwt = jwt_data - if not verify_token_claims(jwt_data[config.user_claims_key]): - raise UserClaimsVerificationError('User claims verification failed') + verify_token_claims(jwt_data) _load_user(jwt_data[config.identity_claim_key]) return fn(*args, **kwargs) return wrapper @@ -61,8 +59,7 @@ def wrapper(*args, **kwargs): try: jwt_data = _decode_jwt_from_request(request_type='access') ctx_stack.top.jwt = jwt_data - if not verify_token_claims(jwt_data[config.user_claims_key]): - raise UserClaimsVerificationError('User claims verification failed') + verify_token_claims(jwt_data) _load_user(jwt_data[config.identity_claim_key]) except (NoAuthorizationError, InvalidHeaderError): pass @@ -93,8 +90,7 @@ def wrapper(*args, **kwargs): now = timegm(datetime.utcnow().utctimetuple()) if fresh < now: raise FreshTokenRequired('Fresh token required') - if not verify_token_claims(jwt_data[config.user_claims_key]): - raise UserClaimsVerificationError('User claims verification failed') + verify_token_claims(jwt_data) _load_user(jwt_data[config.identity_claim_key]) return fn(*args, **kwargs) return wrapper @@ -126,21 +122,6 @@ def _load_user(identity): ctx_stack.top.jwt_user = user -def _token_blacklisted(decoded_token, request_type): - if not config.blacklist_enabled: - return False - if not has_token_in_blacklist_callback(): - raise RuntimeError("A token_in_blacklist_callback must be provided via " - "the '@token_in_blacklist_loader' if " - "JWT_BLACKLIST_ENABLED is True") - - if config.blacklist_access_tokens and request_type == 'access': - return token_in_blacklist(decoded_token) - if config.blacklist_refresh_tokens and request_type == 'refresh': - return token_in_blacklist(decoded_token) - return False - - def _decode_jwt_from_headers(): header_name = config.header_name header_type = config.header_type @@ -207,11 +188,9 @@ def _decode_jwt_from_request(request_type): decoded_token = _decode_jwt_from_cookies(request_type) # Make sure the type of token we received matches the request type we expect - if decoded_token['type'] != request_type: - raise WrongTokenError('Only {} tokens can access this endpoint'.format(request_type)) + verify_token_type(decoded_token, expected_type=request_type) # If blacklisting is enabled, see if this token has been revoked - if _token_blacklisted(decoded_token, request_type): - raise RevokedTokenError('Token has been revoked') + verify_token_not_blacklisted(decoded_token, request_type) return decoded_token diff --git a/setup.py b/setup.py index eb4dea87..14c05170 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,17 @@ """ Flask-JWT-Extended -------------------- +------------------ Flask-Login provides jwt endpoint protection for Flask. """ +import io +import re from setuptools import setup +with io.open('flask_jwt_extended/__init__.py', encoding='utf-8') as f: + version = re.search(r"__version__ = '(.+)'", f.read()).group(1) + setup(name='Flask-JWT-Extended', - version='3.7.0', + version=version, url='https://github.com/vimalloc/flask-jwt-extended', license='MIT', author='Landon Gilbert-Bland', @@ -18,7 +23,7 @@ zip_safe=False, platforms='any', install_requires=[ - 'Werkzeug>=0.14', # needed for samestie cookie functionality + 'Werkzeug>=0.14', # Needed for SameSite cookie functionality 'Flask', 'PyJWT', ], diff --git a/tests/test_blacklist.py b/tests/test_blacklist.py index 8ede97bd..817f5062 100644 --- a/tests/test_blacklist.py +++ b/tests/test_blacklist.py @@ -111,9 +111,9 @@ def test_no_blacklist_callback_method_provided(app): with app.test_request_context(): access_token = create_access_token('username') - with pytest.raises(RuntimeError): - test_client = app.test_client() - test_client.get('/protected', headers=make_headers(access_token)) + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + assert response.status_code == 500 def test_revoked_token_of_different_type(app): diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 30b4964a..a42db897 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -217,12 +217,12 @@ def test_setting_cookies_wihout_cookies_enabled(app): app.config['JWT_TOKEN_LOCATION'] = ['headers'] test_client = app.test_client() - with pytest.raises(RuntimeWarning): - test_client.get('/access_token') - with pytest.raises(RuntimeWarning): - test_client.get('/refresh_token') - with pytest.raises(RuntimeWarning): - test_client.get('/delete_tokens') + response = test_client.get('/access_token') + assert response.status_code == 500 + response = test_client.get('/refresh_token') + assert response.status_code == 500 + response = test_client.get('/delete_tokens') + assert response.status_code == 500 def test_default_cookie_options(app): diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index 8ef20890..96e47edf 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -67,7 +67,7 @@ def test_jwt_required(app): response = test_client.get(url, headers=make_headers(refresh_token)) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 422 - assert json_data == {'msg': 'Only access tokens can access this endpoint'} + assert json_data == {'msg': 'Only access tokens are allowed'} def test_fresh_jwt_required(app): @@ -110,7 +110,7 @@ def test_fresh_jwt_required(app): response = test_client.get(url, headers=make_headers(refresh_token)) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 422 - assert json_data == {'msg': 'Only access tokens can access this endpoint'} + assert json_data == {'msg': 'Only access tokens are allowed'} # Test with custom response @jwtM.needs_fresh_token_loader @@ -135,12 +135,12 @@ def test_refresh_jwt_required(app): response = test_client.get(url, headers=make_headers(fresh_access_token)) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 422 - assert json_data == {'msg': 'Only refresh tokens can access this endpoint'} + assert json_data == {'msg': 'Only refresh tokens are allowed'} response = test_client.get(url, headers=make_headers(access_token)) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 422 - assert json_data == {'msg': 'Only refresh tokens can access this endpoint'} + assert json_data == {'msg': 'Only refresh tokens are allowed'} response = test_client.get(url, headers=None) json_data = json.loads(response.get_data(as_text=True)) @@ -176,7 +176,7 @@ def test_jwt_optional(app): response = test_client.get(url, headers=make_headers(refresh_token)) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 422 - assert json_data == {'msg': 'Only access tokens can access this endpoint'} + assert json_data == {'msg': 'Only access tokens are allowed'} response = test_client.get(url, headers=None) json_data = json.loads(response.get_data(as_text=True))