From a0e59a696dd9e5d5d2020ed570861cfd2919e957 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 20 Aug 2017 18:19:16 -0600 Subject: [PATCH 01/18] Initial messing around with pytest instead of unittest --- tests/app.py | 304 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 tests/app.py diff --git a/tests/app.py b/tests/app.py new file mode 100644 index 00000000..a266de3b --- /dev/null +++ b/tests/app.py @@ -0,0 +1,304 @@ +import pytest +from datetime import timedelta +from flask import Flask, jsonify, json + +from flask_jwt_extended import ( + JWTManager, create_access_token, create_refresh_token, jwt_required, + jwt_refresh_token_required, fresh_jwt_required, jwt_optional, + get_current_user +) + +RSA_PRIVATE = """ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP +DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo +xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB +AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 +NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D +G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H +bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ +Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ +tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 +cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA +mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv +lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d +E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== +-----END RSA PRIVATE KEY----- +""" + +RSA_PUBLIC = """ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd +E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 +mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= +-----END RSA PUBLIC KEY----- +""" + + +def cartesian_product_general_configs(): + jwt_identity_claims = ['identity', 'sub'] + + configs = [] + for identity in jwt_identity_claims: + configs.append({ + 'JWT_SECRET_KEY': 'testing_secret_key', + 'JWT_ALGORITHM': 'HS256', + 'JWT_IDENTITY_CLAIM': identity + }) + configs.append({ + 'JWT_PUBLIC_KEY': RSA_PUBLIC, + 'JWT_PRIVATE_KEY': RSA_PRIVATE, + 'JWT_ALGORITHM': 'RS256', + 'JWT_IDENTITY_CLAIM': identity + }) + return configs + + +def cartesian_product_header_configs(): + token_locations = ['headers', ['cookies', 'headers']] + header_names = ['Authorization', 'Foo'] + header_types = ['Bearer', 'JWT', ''] + + configs = [] + for location in token_locations: + for header_name in header_names: + for header_type in header_types: + config_combination = { + 'JWT_TOKEN_LOCATION': location, + 'JWT_HEADER_NAME': header_name, + 'JWT_HEADER_TYPE': header_type + } + configs.append(config_combination) + return configs + + +HEADER_COMBINATIONS = cartesian_product_header_configs() +CONFIG_COMBINATIONS = cartesian_product_general_configs() + + +@pytest.fixture(scope='module', params=CONFIG_COMBINATIONS) +def app(request): + app = Flask(__name__) + + for key, value in request.param.items(): + app.config[key] = value + + JWTManager(app) + + @app.route('/fresh_access_jwt', methods=['POST']) + def fresh_access_jwt(): + access_token = create_access_token('username', fresh=True) + return jsonify(jwt=access_token) + + @app.route('/not_fresh_access_jwt', methods=['POST']) + def not_fresh_access_jwt(): + access_token = create_access_token('username', fresh=False) + return jsonify(jwt=access_token) + + @app.route('/custom_expires_access_jwt', methods=['POST']) + def custom_expires_access(): + expires = timedelta(minutes=5) + access_token = create_access_token('username', expires_delta=expires) + return jsonify(jwt=access_token) + + @app.route('/refresh_jwt', methods=['POST']) + def refresh_jwt(): + refresh_token = create_refresh_token('username') + return jsonify(jwt=refresh_token) + + @app.route('/custom_expires_refresh_jwt', methods=['POST']) + def custom_expires_refresh_jwt(): + expires = timedelta(minutes=5) + refresh_token = create_refresh_token('username', expires_delta=expires) + return jsonify(jwt=refresh_token) + + @app.route('/protected', methods=['GET', 'POST']) + @jwt_required + def protected(): + return jsonify(foo='bar') + + @app.route('/fresh_protected', methods=['GET', 'POST']) + @fresh_jwt_required + def fresh_protected(): + return jsonify(foo='bar') + + @app.route('/refresh_protected', methods=['GET', 'POST']) + @jwt_refresh_token_required + def refresh_protected(): + return jsonify(foo='bar') + + @app.route('/optional_protected', methods=['GET', 'POST']) + @jwt_optional + def optional_protected(): + if get_current_user(): + return jsonify(foo='baz') + else: + return jsonify(foo='bar') + + @app.route('/not_protected', methods=['GET', 'POST']) + def not_protected(): + return jsonify(foo='bar') + + return app + + +@pytest.fixture(scope='module', params=HEADER_COMBINATIONS) +def headers_app(request, app): + for key, value in request.param.items(): + app.config[key] = value + return app + + +def fresh_login(test_client): + response = test_client.post('/fresh_access_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + +def non_fresh_login(test_client): + response = test_client.post('/not_fresh_access_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + +def make_request(test_client, request_type, request_url, headers=None): + request_type = getattr(test_client, request_type.lower()) + return request_type( + request_url, + content_type='application/json', + headers=headers + ) + + +def make_jwt_headers_request(test_client, jwt, request_type, request_url): + app = test_client.application + header_name = app.config['JWT_HEADER_NAME'] + header_type = app.config['JWT_HEADER_TYPE'] + headers = {header_name: '{} {}'.format(header_type, jwt).strip()} + return make_request(test_client, request_type, request_url, headers=headers) + + +@pytest.mark.parametrize("fail_endpoint", [ + '/protected', + '/fresh_protected', + '/refresh_protected', +]) +@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) +def test_blocked_endpoints_without_jwt(app, fail_endpoint, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + test_client = app.test_client() + response = make_request(test_client, 'GET', fail_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + expected_errors = ( + {'msg': 'Missing Authorization Header'}, + {'msg': 'Missing cookie "access_token_cookie"'}, + {'msg': 'Missing cookie "refresh_token_cookie"'}, + {'msg': 'Missing JWT in headers and cookies'}, + ) + assert response.status_code == 401 + assert json_data in expected_errors + + +@pytest.mark.parametrize("success_endpoint", [ + '/optional_protected', + '/not_protected', +]) +@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) +def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + test_client = app.test_client() + response = make_request(test_client, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/fresh_protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + fresh_jwt = fresh_login(test_client) + response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) +def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + fresh_jwt = fresh_login(test_client) + response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) + 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'} + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + jwt = non_fresh_login(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", [ + '/refresh_protected', + '/fresh_protected' +]) +def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + jwt = non_fresh_login(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + expected_errors = ( + (422, {'msg': 'Only refresh tokens can access this endpoint'}), + (401, {'msg': 'Fresh token required'}) + ) + assert (response.status_code, json_data) in expected_errors + +# TODO test sending in the wrong header name/type with a valid token +# TODO test that verifies the jwt identity claim actually changes (sub/identity) +# TODO when using cookies with csrf, test GET and POST requests + + +# Various options we want to test stuff here (with different expectations for +# success or failure) +# - JWT in cookies and pass in with cookies +# - JWT in cookies and pass in with headers +# - JWT in headers and pass in with cookies +# - JWT in headers and pass in with headers +# - JWT in headers and cookies and pass in with cookies +# - JWT in headers and cookies and pass in with headers +# +# Everything we want to actually test with the above configurations: +# - all protected endpoints with expected jwts +# - all protected endpoints with unexpected jwts +# - all protected endpoints with expired jwts +# - all protected endpoints with tampered with jwts +# - all protected endpoints with tampered with no jwts +# - all protected endpoints with tampered with no revoked jts +# - all protected endpoints with tampered with no user loader from jwts +# - all protected endpoints with tampered with no no user loader from jwts +# - all protected endpoints with tampered with verified claims in jwts +# - all protected endpoints with tampered with failed verified claims in jwts From 03fb2f8fe5bdf36a65dd5a60b87a6c24476b9f0a Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 20 Aug 2017 19:28:23 -0600 Subject: [PATCH 02/18] More pytests --- tests/app.py | 100 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/tests/app.py b/tests/app.py index a266de3b..b8e1189b 100644 --- a/tests/app.py +++ b/tests/app.py @@ -76,7 +76,7 @@ def cartesian_product_header_configs(): CONFIG_COMBINATIONS = cartesian_product_general_configs() -@pytest.fixture(scope='module', params=CONFIG_COMBINATIONS) +@pytest.fixture(scope='function', params=CONFIG_COMBINATIONS) def app(request): app = Flask(__name__) @@ -142,14 +142,14 @@ def not_protected(): return app -@pytest.fixture(scope='module', params=HEADER_COMBINATIONS) +@pytest.fixture(scope='function', params=HEADER_COMBINATIONS) def headers_app(request, app): for key, value in request.param.items(): app.config[key] = value return app -def fresh_login(test_client): +def get_fresh_jwt(test_client): response = test_client.post('/fresh_access_jwt') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 @@ -157,7 +157,7 @@ def fresh_login(test_client): return json_data['jwt'] -def non_fresh_login(test_client): +def get_non_fresh_jwt(test_client): response = test_client.post('/not_fresh_access_jwt') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 @@ -165,6 +165,14 @@ def non_fresh_login(test_client): return json_data['jwt'] +def get_refresh_jwt(test_client): + response = test_client.post('/refresh_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + def make_request(test_client, request_type, request_url, headers=None): request_type = getattr(test_client, request_type.lower()) return request_type( @@ -227,7 +235,7 @@ def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location) ]) def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_endpoint): test_client = headers_app.test_client() - fresh_jwt = fresh_login(test_client) + fresh_jwt = get_fresh_jwt(test_client) response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) @@ -238,7 +246,7 @@ def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_end @pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoint): test_client = headers_app.test_client() - fresh_jwt = fresh_login(test_client) + fresh_jwt = get_fresh_jwt(test_client) response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) json_data = json.loads(response.get_data(as_text=True)) @@ -253,7 +261,7 @@ def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoi ]) def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success_endpoint): test_client = headers_app.test_client() - jwt = non_fresh_login(test_client) + jwt = get_non_fresh_jwt(test_client) response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) @@ -267,7 +275,7 @@ def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success ]) def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_endpoint): test_client = headers_app.test_client() - jwt = non_fresh_login(test_client) + jwt = get_non_fresh_jwt(test_client) response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) json_data = json.loads(response.get_data(as_text=True)) @@ -277,9 +285,81 @@ def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_en ) assert (response.status_code, json_data) in expected_errors -# TODO test sending in the wrong header name/type with a valid token -# TODO test that verifies the jwt identity claim actually changes (sub/identity) + +@pytest.mark.parametrize("success_endpoint", [ + '/refresh_protected', + '/not_protected' +]) +def test_accessable_endpoints_with_refresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + jwt = get_refresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", [ + '/fresh_protected', + '/protected', + '/optional_protected' +]) +def test_blocked_endpoints_with_refresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + jwt = get_refresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) + 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'} + + +@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) +def test_bad_header_name_blocks_protected_endpoints(app, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + app.config['JWT_HEADER_NAME'] = 'Foo' + + test_client = app.test_client() + jwt = get_fresh_jwt(test_client) + + headers = {'Authorization': 'Bearer {}'.format(jwt)} + response = make_request(test_client, 'GET', '/protected', headers=headers) + json_data = json.loads(response.get_data(as_text=True)) + + expected_json = ( + {'msg': 'Missing Foo Header'}, + {'msg': 'Missing JWT in headers and cookies'} + ) + assert response.status_code == 401 + assert json_data in expected_json + + +@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) +@pytest.mark.parametrize("header_type", ['Foo', '']) +def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_type): + app.config['JWT_TOKEN_LOCATION'] = token_location + app.config['JWT_HEADER_TYPE'] = header_type + + test_client = app.test_client() + jwt = get_fresh_jwt(test_client) + + headers = {'Authorization': 'Bearer {}'.format(jwt)} + response = make_request(test_client, 'GET', '/protected', headers=headers) + json_data = json.loads(response.get_data(as_text=True)) + + expected_json = ( + {'msg': "Bad Authorization header. Expected value ''"}, + {'msg': "Bad Authorization header. Expected value 'Foo '"} + ) + + assert response.status_code == 422 + assert json_data in expected_json + +# TODO test sending in headers when cookie_locations and vice versa # TODO when using cookies with csrf, test GET and POST requests +# TODO test that verifies the jwt identity claim actually changes (sub/identity) +# TODO test possible combinations for jwt_optional # Various options we want to test stuff here (with different expectations for From 0f624c6b8d56c013a8eee196a80954c41c66f0f1 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 20 Aug 2017 23:52:22 -0600 Subject: [PATCH 03/18] Work required to make jwt in cookies tests --- tests/app.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/tests/app.py b/tests/app.py index b8e1189b..c736b74f 100644 --- a/tests/app.py +++ b/tests/app.py @@ -1,11 +1,12 @@ import pytest from datetime import timedelta from flask import Flask, jsonify, json +from werkzeug.http import parse_cookie from flask_jwt_extended import ( JWTManager, create_access_token, create_refresh_token, jwt_required, jwt_refresh_token_required, fresh_jwt_required, jwt_optional, - get_current_user + get_current_user, set_access_cookies ) RSA_PRIVATE = """ @@ -72,6 +73,25 @@ def cartesian_product_header_configs(): return configs +def cartesian_product_cookie_configs(): + token_locations = [['cookies'], ['cookies', 'headers']] + access_cookie_names = ['access_token_cookie', 'access_foo'] + refresh_cookie_names = ['refresh_token_cookie', 'refresh_foo'] + + configs = [] + for location in token_locations: + for access_name in access_cookie_names: + for refresh_name in refresh_cookie_names: + config_combination = { + 'JWT_TOKEN_LOCATION': location, + 'JWT_ACCESS_COOKIE_NAME': access_name, + 'JWT_REFRESH_COOKIE_NAME': refresh_name + } + configs.append(config_combination) + return configs + + +COOKIE_COMBINATIONS = cartesian_product_cookie_configs() HEADER_COMBINATIONS = cartesian_product_header_configs() CONFIG_COMBINATIONS = cartesian_product_general_configs() @@ -90,6 +110,13 @@ def fresh_access_jwt(): access_token = create_access_token('username', fresh=True) return jsonify(jwt=access_token) + @app.route('/cookie_fresh_access_jwt', methods=['POST']) + def cookie_fresh_access_jwt(): + access_token = create_access_token('username', fresh=True) + resp = jsonify(success=True) + set_access_cookies(resp, access_token) + return resp + @app.route('/not_fresh_access_jwt', methods=['POST']) def not_fresh_access_jwt(): access_token = create_access_token('username', fresh=False) @@ -149,6 +176,13 @@ def headers_app(request, app): return app +@pytest.fixture(scope='function', params=COOKIE_COMBINATIONS) +def cookies_app(request, app): + for key, value in request.param.items(): + app.config[key] = value + return app + + def get_fresh_jwt(test_client): response = test_client.post('/fresh_access_jwt') json_data = json.loads(response.get_data(as_text=True)) @@ -173,7 +207,29 @@ def get_refresh_jwt(test_client): return json_data['jwt'] -def make_request(test_client, request_type, request_url, headers=None): +def get_cookie_fresh_jwt(test_client): + app = test_client.application + access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + + response = test_client.post('/cookie_fresh_access_jwt') + assert response.status_code == 200 + + cookies = response.headers.getlist('Set-Cookie') + for cookie in cookies: + parsed_cookie = parse_cookie(cookie) + for c_key, c_val in parsed_cookie.items(): + if c_key == access_cookie_name: + return c_val + raise Exception('jwt cooke value not found') + + +def make_request(test_client, request_type, request_url, headers=None, cookies=None): + if cookies is None: + cookies = {} + if cookies: + for c_key, c_val in cookies.items(): + test_client.set_cookie('/', c_key, c_val) + request_type = getattr(test_client, request_type.lower()) return request_type( request_url, @@ -190,6 +246,13 @@ def make_jwt_headers_request(test_client, jwt, request_type, request_url): return make_request(test_client, request_type, request_url, headers=headers) +def make_jwt_cookies_request(test_client, jwt, request_type, request_url): + app = test_client.application + cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + cookies = {cookie_name: jwt} + return make_request(test_client, request_type, request_url, cookies=cookies) + + @pytest.mark.parametrize("fail_endpoint", [ '/protected', '/fresh_protected', @@ -356,10 +419,27 @@ def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_ assert response.status_code == 422 assert json_data in expected_json + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/fresh_protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_fresh_jwt_in_cookies(cookies_app, success_endpoint): + test_client = cookies_app.test_client() + fresh_jwt = get_cookie_fresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + # TODO test sending in headers when cookie_locations and vice versa # TODO when using cookies with csrf, test GET and POST requests # TODO test that verifies the jwt identity claim actually changes (sub/identity) # TODO test possible combinations for jwt_optional +# TODO simple test that the other cookie overrides are working # Various options we want to test stuff here (with different expectations for From 496e139fead188f27b8e75adc03ff856e72b0e2e Mon Sep 17 00:00:00 2001 From: Landon GB Date: Fri, 25 Aug 2017 16:08:57 -0600 Subject: [PATCH 04/18] Store JWTManager in app.extensions['flask-jwt-extended'] instead of app.jwt_manager --- flask_jwt_extended/jwt_manager.py | 4 +++- flask_jwt_extended/utils.py | 4 ++-- tests/test_jwt_manager.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/flask_jwt_extended/jwt_manager.py b/flask_jwt_extended/jwt_manager.py index a3da2154..6a37f8ac 100644 --- a/flask_jwt_extended/jwt_manager.py +++ b/flask_jwt_extended/jwt_manager.py @@ -64,7 +64,9 @@ def init_app(self, app): :param app: A flask application """ # Save this so we can use it later in the extension - app.jwt_manager = self + if not hasattr(app, 'extensions'): # pragma: no cover + app.extensions = {} + app.extensions['flask-jwt-extended'] = self # Set all the default configurations for this extension self._set_default_configuration_options(app) diff --git a/flask_jwt_extended/utils.py b/flask_jwt_extended/utils.py index 7d6074f3..9dfd5e93 100644 --- a/flask_jwt_extended/utils.py +++ b/flask_jwt_extended/utils.py @@ -70,8 +70,8 @@ def decode_token(encoded_token): def _get_jwt_manager(): try: - return current_app.jwt_manager - except AttributeError: # pragma: no cover + return current_app.extensions['flask-jwt-extended'] + except KeyError: # pragma: no cover raise RuntimeError("You must initialize a JWTManager with this flask " "application before using this method") diff --git a/tests/test_jwt_manager.py b/tests/test_jwt_manager.py index b981b95a..106a026a 100644 --- a/tests/test_jwt_manager.py +++ b/tests/test_jwt_manager.py @@ -24,11 +24,11 @@ def _parse_callback_result(self, result): def test_init_app(self): jwt_manager = JWTManager() jwt_manager.init_app(self.app) - self.assertIsInstance(jwt_manager, JWTManager) + self.assertEqual(jwt_manager, self.app.extensions['flask-jwt-extended']) def test_class_init(self): jwt_manager = JWTManager(self.app) - self.assertIsInstance(jwt_manager, JWTManager) + self.assertEqual(jwt_manager, self.app.extensions['flask-jwt-extended']) def test_default_user_claims_callback(self): identity = 'foobar' From 249b6a2bd652c98da0f340fc2fe427120c2b28a5 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Fri, 25 Aug 2017 16:10:01 -0600 Subject: [PATCH 05/18] More tests --- tests/app.py | 162 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 18 deletions(-) diff --git a/tests/app.py b/tests/app.py index c736b74f..55bb5f5b 100644 --- a/tests/app.py +++ b/tests/app.py @@ -6,7 +6,8 @@ from flask_jwt_extended import ( JWTManager, create_access_token, create_refresh_token, jwt_required, jwt_refresh_token_required, fresh_jwt_required, jwt_optional, - get_current_user, set_access_cookies + get_current_user, set_access_cookies, + set_refresh_cookies ) RSA_PRIVATE = """ @@ -122,6 +123,13 @@ def not_fresh_access_jwt(): access_token = create_access_token('username', fresh=False) return jsonify(jwt=access_token) + @app.route('/cookie_not_fresh_access_jwt', methods=['POST']) + def cookie_not_fresh_access_jwt(): + access_token = create_access_token('username', fresh=False) + resp = jsonify(success=True) + set_access_cookies(resp, access_token) + return resp + @app.route('/custom_expires_access_jwt', methods=['POST']) def custom_expires_access(): expires = timedelta(minutes=5) @@ -133,6 +141,13 @@ def refresh_jwt(): refresh_token = create_refresh_token('username') return jsonify(jwt=refresh_token) + @app.route('/cookie_refresh_jwt', methods=['POST']) + def cookie_refresh_jwt(): + refresh_token = create_refresh_token('username') + resp = jsonify(success=True) + set_refresh_cookies(resp, refresh_token) + return resp + @app.route('/custom_expires_refresh_jwt', methods=['POST']) def custom_expires_refresh_jwt(): expires = timedelta(minutes=5) @@ -207,22 +222,43 @@ def get_refresh_jwt(test_client): return json_data['jwt'] -def get_cookie_fresh_jwt(test_client): - app = test_client.application - access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - - response = test_client.post('/cookie_fresh_access_jwt') - assert response.status_code == 200 - +def _get_jwt_from_response_cookie(response, cookie_name): cookies = response.headers.getlist('Set-Cookie') for cookie in cookies: parsed_cookie = parse_cookie(cookie) for c_key, c_val in parsed_cookie.items(): - if c_key == access_cookie_name: + if c_key == cookie_name: return c_val raise Exception('jwt cooke value not found') +def get_cookie_fresh_jwt(test_client): + response = test_client.post('/cookie_fresh_access_jwt') + assert response.status_code == 200 + + app = test_client.application + access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + return _get_jwt_from_response_cookie(response, access_cookie_name) + + +def get_cookie_non_fresh_jwt(test_client): + response = test_client.post('/cookie_not_fresh_access_jwt') + assert response.status_code == 200 + + app = test_client.application + access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + return _get_jwt_from_response_cookie(response, access_cookie_name) + + +def get_cookie_refresh_jwt(test_client): + response = test_client.post('/cookie_refresh_jwt') + assert response.status_code == 200 + + app = test_client.application + access_cookie_name = app.config['JWT_REFRESH_COOKIE_NAME'] + return _get_jwt_from_response_cookie(response, access_cookie_name) + + def make_request(test_client, request_type, request_url, headers=None, cookies=None): if cookies is None: cookies = {} @@ -271,8 +307,8 @@ def test_blocked_endpoints_without_jwt(app, fail_endpoint, token_location): {'msg': 'Missing cookie "refresh_token_cookie"'}, {'msg': 'Missing JWT in headers and cookies'}, ) - assert response.status_code == 401 assert json_data in expected_errors + assert response.status_code == 401 @pytest.mark.parametrize("success_endpoint", [ @@ -286,8 +322,8 @@ def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location) response = make_request(test_client, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 assert json_data == {'foo': 'bar'} + assert response.status_code == 200 @pytest.mark.parametrize("success_endpoint", [ @@ -302,8 +338,8 @@ def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_end response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 assert json_data == {'foo': 'bar'} + assert response.status_code == 200 @pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) @@ -313,8 +349,8 @@ def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoi response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) 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 response.status_code == 422 @pytest.mark.parametrize("success_endpoint", [ @@ -328,8 +364,8 @@ def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 assert json_data == {'foo': 'bar'} + assert response.status_code == 200 @pytest.mark.parametrize("failure_endpoint", [ @@ -359,8 +395,8 @@ def test_accessable_endpoints_with_refresh_jwt_in_headers(headers_app, success_e response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 assert json_data == {'foo': 'bar'} + assert response.status_code == 200 @pytest.mark.parametrize("failure_endpoint", [ @@ -374,8 +410,8 @@ def test_blocked_endpoints_with_refresh_jwt_in_headers(headers_app, failure_endp response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) 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 response.status_code == 422 @pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) @@ -394,8 +430,8 @@ def test_bad_header_name_blocks_protected_endpoints(app, token_location): {'msg': 'Missing Foo Header'}, {'msg': 'Missing JWT in headers and cookies'} ) - assert response.status_code == 401 assert json_data in expected_json + assert response.status_code == 401 @pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) @@ -416,8 +452,8 @@ def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_ {'msg': "Bad Authorization header. Expected value 'Foo '"} ) - assert response.status_code == 422 assert json_data in expected_json + assert response.status_code == 422 @pytest.mark.parametrize("success_endpoint", [ @@ -432,14 +468,104 @@ def test_accessable_endpoints_with_fresh_jwt_in_cookies(cookies_app, success_end response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} assert response.status_code == 200 + + +# TODO when using cookies, actually send the wrong cookie type (refresh/access) +# into the header that expects the other type. The cookies have different +# names, so this test doesn't actually test that case +@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) +def test_blocked_endpoints_with_fresh_jwt_in_headers(cookies_app, failure_endpoint): + test_client = cookies_app.test_client() + fresh_jwt = get_cookie_fresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', failure_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + expected_errors = ( + {'msg': 'Missing cookie "{}"'.format(cookies_app.config['JWT_REFRESH_COOKIE_NAME'])}, + {'msg': 'Missing JWT in headers and cookies'} + ) + assert json_data in expected_errors + assert response.status_code == 401 + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_non_fresh_jwt_in_cookies(cookies_app, success_endpoint): + test_client = cookies_app.test_client() + fresh_jwt = get_cookie_non_fresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +@pytest.mark.parametrize("failure_endpoint", [ + '/refresh_protected', + '/fresh_protected' +]) +def test_blocked_endpoints_with_non_fresh_jwt_in_cookies(cookies_app, failure_endpoint): + test_client = cookies_app.test_client() + fresh_jwt = get_cookie_non_fresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', failure_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + refresh_cookie_name = cookies_app.config['JWT_REFRESH_COOKIE_NAME'] + expected_errors = ( + {'msg': 'Missing cookie "{}"'.format(refresh_cookie_name)}, + {'msg': 'Missing JWT in headers and cookies'}, + {'msg': 'Fresh token required'} + ) + assert json_data in expected_errors + assert response.status_code == 401 + + +@pytest.mark.parametrize("success_endpoint", [ + '/refresh_protected', + '/not_protected' +]) +def test_accessable_endpoints_with_refresh_jwt_in_cookies(cookies_app, success_endpoint): + test_client = cookies_app.test_client() + refresh_jwt = get_cookie_refresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, refresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +@pytest.mark.parametrize("failure_endpoint", [ + '/fresh_protected', + '/protected', + '/optional_protected' +]) +def test_blocked_endpoints_with_refresh_jwt_in_cookies(cookies_app, failure_endpoint): + test_client = cookies_app.test_client() + refresh_jwt = get_cookie_refresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, refresh_jwt, 'GET', failure_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + # TODO is this right? I would expect an error about missing the cookie. I + # think this is broke as we are only sending in the access cookie + # not the refresh cookie when doing make_jwt_cookies_request + expected_errors = ( + {'msg': 'Only access tokens can access this endpoint'}, + ) + assert json_data in expected_errors + assert response.status_code == 422 + # TODO test sending in headers when cookie_locations and vice versa # TODO when using cookies with csrf, test GET and POST requests # TODO test that verifies the jwt identity claim actually changes (sub/identity) # TODO test possible combinations for jwt_optional # TODO simple test that the other cookie overrides are working +# TODO test having the access and refresh cookie be the same name? # Various options we want to test stuff here (with different expectations for From c87f1867ebb527da27830795965cd36687ca8128 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Fri, 20 Oct 2017 14:44:57 -0600 Subject: [PATCH 06/18] Move claims verification after jwt is on the context (refs #90) --- flask_jwt_extended/view_decorators.py | 15 +++++++-------- tests/{app.py => old.py} | 0 tests/test_claims_verification.py | 0 tests/test_utils.py | 0 tests/test_view_decorators.py | 0 5 files changed, 7 insertions(+), 8 deletions(-) rename tests/{app.py => old.py} (100%) create mode 100644 tests/test_claims_verification.py create mode 100644 tests/test_utils.py create mode 100644 tests/test_view_decorators.py diff --git a/flask_jwt_extended/view_decorators.py b/flask_jwt_extended/view_decorators.py index 4f68ba15..0a1f9d3d 100644 --- a/flask_jwt_extended/view_decorators.py +++ b/flask_jwt_extended/view_decorators.py @@ -34,6 +34,8 @@ def jwt_required(fn): def wrapper(*args, **kwargs): 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]): + raise UserClaimsVerificationError('User claims verification failed') _load_user(jwt_data[config.identity_claim]) return fn(*args, **kwargs) return wrapper @@ -58,6 +60,8 @@ 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]): + raise UserClaimsVerificationError('User claims verification failed') _load_user(jwt_data[config.identity_claim]) except (NoAuthorizationError, InvalidHeaderError): pass @@ -77,12 +81,12 @@ def fresh_jwt_required(fn): """ @wraps(fn) def wrapper(*args, **kwargs): - # Check if the token is fresh jwt_data = _decode_jwt_from_request(request_type='access') + ctx_stack.top.jwt = jwt_data if not jwt_data['fresh']: raise FreshTokenRequired('Fresh token required') - - ctx_stack.top.jwt = jwt_data + if not verify_token_claims(jwt_data[config.user_claims]): + raise UserClaimsVerificationError('User claims verification failed') _load_user(jwt_data[config.identity_claim]) return fn(*args, **kwargs) return wrapper @@ -214,11 +218,6 @@ def _decode_jwt_from_request(request_type): if decoded_token['type'] != request_type: raise WrongTokenError('Only {} tokens can access this endpoint'.format(request_type)) - # Check if the custom claims in access tokens are valid - if request_type == 'access': - if not verify_token_claims(decoded_token[config.user_claims]): - raise UserClaimsVerificationError('User claims verification failed') - # If blacklisting is enabled, see if this token has been revoked if _token_blacklisted(decoded_token, request_type): raise RevokedTokenError('Token has been revoked') diff --git a/tests/app.py b/tests/old.py similarity index 100% rename from tests/app.py rename to tests/old.py diff --git a/tests/test_claims_verification.py b/tests/test_claims_verification.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py new file mode 100644 index 00000000..e69de29b From 8badf1928e63edfc0ca018e13a69efac6f54ad09 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Fri, 20 Oct 2017 15:29:24 -0600 Subject: [PATCH 07/18] Better setup for unit tests, start moving everything over to pytest --- tests/{test_utils.py => __init__.py} | 0 tests/test_claims_verification.py | 116 +++++ tests/test_config.py | 491 ++++++++++-------- tests/test_user_claims_loader.py | 104 ++++ tests/test_user_loader.py | 210 +++----- tests/test_view_decorators.py | 179 +++++++ tests/utils.py | 6 + tests_old/app.py | 464 +++++++++++++++++ {tests => tests_old}/test_blacklist.py | 0 tests_old/test_config.py | 229 ++++++++ .../test_jwt_encode_decode.py | 0 {tests => tests_old}/test_jwt_manager.py | 0 .../test_protected_endpoints.py | 0 .../test_user_claims_verification.py | 0 tests_old/test_user_loader.py | 135 +++++ tox.ini | 2 +- 16 files changed, 1583 insertions(+), 353 deletions(-) rename tests/{test_utils.py => __init__.py} (100%) create mode 100644 tests/test_user_claims_loader.py create mode 100644 tests/utils.py create mode 100644 tests_old/app.py rename {tests => tests_old}/test_blacklist.py (100%) create mode 100644 tests_old/test_config.py rename {tests => tests_old}/test_jwt_encode_decode.py (100%) rename {tests => tests_old}/test_jwt_manager.py (100%) rename {tests => tests_old}/test_protected_endpoints.py (100%) rename {tests => tests_old}/test_user_claims_verification.py (100%) create mode 100644 tests_old/test_user_loader.py diff --git a/tests/test_utils.py b/tests/__init__.py similarity index 100% rename from tests/test_utils.py rename to tests/__init__.py diff --git a/tests/test_claims_verification.py b/tests/test_claims_verification.py index e69de29b..dad565f2 100644 --- a/tests/test_claims_verification.py +++ b/tests/test_claims_verification.py @@ -0,0 +1,116 @@ +import pytest +from flask import Flask, jsonify, json + +from flask_jwt_extended import ( + JWTManager, jwt_required, create_access_token, get_jwt_identity, + fresh_jwt_required, jwt_optional +) +from tests.utils import get_jwt_manager, make_headers + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + jwt = JWTManager(app) + + @jwt.user_claims_loader + def add_user_claims(identity): + return {'foo': 'bar'} + + @app.route('/protected1', methods=['GET']) + @jwt_required + def protected1(): + return jsonify(foo='bar') + + @app.route('/protected2', methods=['GET']) + @fresh_jwt_required + def protected2(): + return jsonify(foo='bar') + + @app.route('/protected3', methods=['GET']) + @jwt_optional + def protected3(): + return jsonify(foo='bar') + + return app + + +@pytest.mark.parametrize("url", ['/protected1', '/protected2', '/protected3']) +def test_successful_claims_validation(app, url): + jwt = get_jwt_manager(app) + + @jwt.claims_verification_loader + def user_load_callback(user_claims): + return user_claims == {'foo': 'bar'} + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username', fresh=True) + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +@pytest.mark.parametrize("url", ['/protected1', '/protected2', '/protected3']) +def test_unsuccessful_claims_validation(app, url): + jwt = get_jwt_manager(app) + + @jwt.claims_verification_loader + def user_load_callback(user_claims): + return False + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username', fresh=True) + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'msg': 'User claims verification failed'} + assert response.status_code == 400 + + +@pytest.mark.parametrize("url", ['/protected1', '/protected2', '/protected3']) +def test_claims_validation_custom_error(app, url): + jwt = get_jwt_manager(app) + + @jwt.claims_verification_loader + def user_load_callback(user_claims): + return False + + @jwt.claims_verification_failed_loader + def custom_error(): + # Make sure that we can get the jwt identity in here if we need it. + user = get_jwt_identity() + return jsonify(msg='claims failed for {}'.format(user)), 404 + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username', fresh=True) + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'msg': 'claims failed for username'} + assert response.status_code == 404 + + +@pytest.mark.parametrize("url", ['/protected1', '/protected2', '/protected3']) +def test_get_jwt_identity_in_verification_method(app, url): + jwt = get_jwt_manager(app) + + @jwt.claims_verification_loader + def user_load_callback(user_claims): + # Make sure that we can get the jwt identity in here if we need it. + user = get_jwt_identity() + return user == 'username' + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username', fresh=True) + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 diff --git a/tests/test_config.py b/tests/test_config.py index 988bad79..7757c424 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,229 +1,276 @@ -import unittest import warnings -from datetime import timedelta +import pytest +from datetime import timedelta from flask import Flask -from flask_jwt_extended.config import config from flask_jwt_extended import JWTManager +from flask_jwt_extended.config import config -class TestEndpoints(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - JWTManager(self.app) - - def test_default_configs(self): - with self.app.test_request_context(): - self.assertEqual(config.token_location, ['headers']) - self.assertEqual(config.jwt_in_cookies, False) - self.assertEqual(config.jwt_in_headers, True) - self.assertEqual(config.header_name, 'Authorization') - self.assertEqual(config.header_type, 'Bearer') - - self.assertEqual(config.access_cookie_name, 'access_token_cookie') - self.assertEqual(config.refresh_cookie_name, 'refresh_token_cookie') - self.assertEqual(config.access_cookie_path, '/') - self.assertEqual(config.refresh_cookie_path, '/') - self.assertEqual(config.cookie_secure, False) - self.assertEqual(config.cookie_domain, None) - self.assertEqual(config.session_cookie, True) - - self.assertEqual(config.csrf_protect, False) - self.assertEqual(config.csrf_request_methods, ['POST', 'PUT', 'PATCH', 'DELETE']) - self.assertEqual(config.csrf_in_cookies, True) - self.assertEqual(config.access_csrf_cookie_name, 'csrf_access_token') - self.assertEqual(config.refresh_csrf_cookie_name, 'csrf_refresh_token') - self.assertEqual(config.access_csrf_cookie_path, '/') - self.assertEqual(config.refresh_csrf_cookie_path, '/') - self.assertEqual(config.access_csrf_header_name, 'X-CSRF-TOKEN') - self.assertEqual(config.refresh_csrf_header_name, 'X-CSRF-TOKEN') - - self.assertEqual(config.access_expires, timedelta(minutes=15)) - self.assertEqual(config.refresh_expires, timedelta(days=30)) - self.assertEqual(config.algorithm, 'HS256') - self.assertEqual(config.is_asymmetric, False) - self.assertEqual(config.blacklist_enabled, False) - self.assertEqual(config.blacklist_checks, ['access', 'refresh']) - self.assertEqual(config.blacklist_access_tokens, True) - self.assertEqual(config.blacklist_refresh_tokens, True) - - self.assertEqual(config.encode_key, self.app.secret_key) - self.assertEqual(config.decode_key, self.app.secret_key) - self.assertEqual(config.cookie_max_age, None) - - self.assertEqual(config.identity_claim, 'identity') - self.assertEqual(config.user_claims, 'user_claims') - - def test_override_configs(self): - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_HEADER_NAME'] = 'TestHeader' - self.app.config['JWT_HEADER_TYPE'] = 'TestType' - - self.app.config['JWT_ACCESS_COOKIE_NAME'] = 'new_access_cookie' - self.app.config['JWT_REFRESH_COOKIE_NAME'] = 'new_refresh_cookie' - self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/access/path' - self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh/path' - self.app.config['JWT_COOKIE_SECURE'] = True - self.app.config['JWT_COOKIE_DOMAIN'] = ".example.com" - self.app.config['JWT_SESSION_COOKIE'] = False - - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.app.config['JWT_CSRF_METHODS'] = ['GET'] - self.app.config['JWT_CSRF_IN_COOKIES'] = False - self.app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'access_csrf_cookie' - self.app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'refresh_csrf_cookie' - self.app.config['JWT_ACCESS_CSRF_COOKIE_PATH'] = '/csrf/access/path' - self.app.config['JWT_REFRESH_CSRF_COOKIE_PATH'] = '/csrf/refresh/path' - self.app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'X-ACCESS-CSRF' - self.app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'X-REFRESH-CSRF' - - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5) - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=5) - self.app.config['JWT_ALGORITHM'] = 'HS512' - - self.app.config['JWT_BLACKLIST_ENABLED'] = True - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' - - self.app.secret_key = 'banana' - - self.app.config['JWT_IDENTITY_CLAIM'] = 'foo' - self.app.config['JWT_USER_CLAIMS'] = 'bar' - - with self.app.test_request_context(): - self.assertEqual(config.token_location, ['cookies']) - self.assertEqual(config.jwt_in_cookies, True) - self.assertEqual(config.jwt_in_headers, False) - self.assertEqual(config.header_name, 'TestHeader') - self.assertEqual(config.header_type, 'TestType') - - self.assertEqual(config.access_cookie_name, 'new_access_cookie') - self.assertEqual(config.refresh_cookie_name, 'new_refresh_cookie') - self.assertEqual(config.access_cookie_path, '/access/path') - self.assertEqual(config.refresh_cookie_path, '/refresh/path') - self.assertEqual(config.cookie_secure, True) - self.assertEqual(config.cookie_domain, ".example.com") - self.assertEqual(config.session_cookie, False) - - self.assertEqual(config.csrf_protect, True) - self.assertEqual(config.csrf_request_methods, ['GET']) - self.assertEqual(config.csrf_in_cookies, False) - self.assertEqual(config.access_csrf_cookie_name, 'access_csrf_cookie') - self.assertEqual(config.refresh_csrf_cookie_name, 'refresh_csrf_cookie') - self.assertEqual(config.access_csrf_cookie_path, '/csrf/access/path') - self.assertEqual(config.refresh_csrf_cookie_path, '/csrf/refresh/path') - self.assertEqual(config.access_csrf_header_name, 'X-ACCESS-CSRF') - self.assertEqual(config.refresh_csrf_header_name, 'X-REFRESH-CSRF') - - self.assertEqual(config.access_expires, timedelta(minutes=5)) - self.assertEqual(config.refresh_expires, timedelta(days=5)) - self.assertEqual(config.algorithm, 'HS512') - - self.assertEqual(config.blacklist_enabled, True) - self.assertEqual(config.blacklist_checks, ['refresh']) - self.assertEqual(config.blacklist_access_tokens, False) - self.assertEqual(config.blacklist_refresh_tokens, True) - - self.assertEqual(config.cookie_max_age, 2147483647) - - self.assertEqual(config.identity_claim, 'foo') - self.assertEqual(config.user_claims, 'bar') - - def test_invalid_config_options(self): - with self.app.test_request_context(): - self.app.config['JWT_TOKEN_LOCATION'] = 'banana' - with self.assertRaises(RuntimeError): - config.token_location - - self.app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies', 'banana'] - with self.assertRaises(RuntimeError): - config.token_location - - self.app.config['JWT_HEADER_NAME'] = '' - with self.app.test_request_context(): - with self.assertRaises(RuntimeError): - config.header_name - - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 'banana' - with self.assertRaises(RuntimeError): - config.access_expires - - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 'banana' - with self.assertRaises(RuntimeError): - config.refresh_expires - - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana' - with self.assertRaises(RuntimeError): - config.blacklist_checks - - self.app.secret_key = None - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.secret_key = '' - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.secret_key = None - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.config['JWT_ALGORITHM'] = 'RS256' - self.app.config['JWT_PUBLIC_KEY'] = None - self.app.config['JWT_PRIVATE_KEY'] = None - with self.assertRaises(RuntimeError): - config.decode_key - with self.assertRaises(RuntimeError): - config.encode_key - - def test_depreciated_options(self): - self.app.config['JWT_CSRF_HEADER_NAME'] = 'Auth' - - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - - # Verify our warnings are thrown - with self.app.test_request_context(): - with warnings.catch_warnings(record=True) as w: - self.assertEqual(config.access_csrf_header_name, 'Auth') - self.assertEqual(config.refresh_csrf_header_name, 'Auth') - self.assertEqual(len(w), 2) - self.assertEqual(w[0].category, DeprecationWarning) - self.assertEqual(w[1].category, DeprecationWarning) - - def test_special_config_options(self): - with self.app.test_request_context(): - # Test changing strings to lists for JWT_TOKEN_LOCATIONS - self.app.config['JWT_TOKEN_LOCATION'] = 'headers' - self.assertEqual(config.token_location, ['headers']) - self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] - self.assertEqual(config.token_location, ['headers']) - self.app.config['JWT_TOKEN_LOCATION'] = 'cookies' - self.assertEqual(config.token_location, ['cookies']) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.assertEqual(config.token_location, ['cookies']) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] - self.assertEqual(config.token_location, ['cookies', 'headers']) - - # Test csrf protect options - self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.assertEqual(config.csrf_protect, False) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.assertEqual(config.csrf_protect, True) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False - self.assertEqual(config.csrf_protect, False) - - def test_asymmetric_encryption_key_handling(self): - self.app.config['JWT_PRIVATE_KEY'] = 'MOCK_RSA_PRIVATE_KEY' - self.app.config['JWT_PUBLIC_KEY'] = 'MOCK_RSA_PUBLIC_KEY' - self.app.config['JWT_ALGORITHM'] = 'RS256' - - with self.app.test_request_context(): - self.assertEqual(config.is_asymmetric, True) - self.assertEqual(config.encode_key, 'MOCK_RSA_PRIVATE_KEY') - self.assertEqual(config.decode_key, 'MOCK_RSA_PUBLIC_KEY') +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + JWTManager(app) + return app + + +def test_default_configs(app): + with app.test_request_context(): + assert config.token_location == ['headers'] + assert config.jwt_in_cookies is False + assert config.jwt_in_headers is True + assert config.header_name == 'Authorization' + assert config.header_type == 'Bearer' + + assert config.access_cookie_name == 'access_token_cookie' + assert config.refresh_cookie_name == 'refresh_token_cookie' + assert config.access_cookie_path == '/' + assert config.refresh_cookie_path == '/' + assert config.cookie_secure is False + assert config.cookie_domain is None + assert config.session_cookie is True + + assert config.csrf_protect is False + assert config.csrf_request_methods == ['POST', 'PUT', 'PATCH', 'DELETE'] + assert config.csrf_in_cookies is True + assert config.access_csrf_cookie_name == 'csrf_access_token' + assert config.refresh_csrf_cookie_name == 'csrf_refresh_token' + assert config.access_csrf_cookie_path == '/' + assert config.refresh_csrf_cookie_path == '/' + assert config.access_csrf_header_name == 'X-CSRF-TOKEN' + assert config.refresh_csrf_header_name == 'X-CSRF-TOKEN' + + assert config.access_expires == timedelta(minutes=15) + assert config.refresh_expires == timedelta(days=30) + 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_access_tokens is True + assert config.blacklist_refresh_tokens is True + + assert config.cookie_max_age is None + + assert config.identity_claim == 'identity' + assert config.user_claims == 'user_claims' + + +def test_override_configs(app): + app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + app.config['JWT_HEADER_NAME'] = 'TestHeader' + app.config['JWT_HEADER_TYPE'] = 'TestType' + + app.config['JWT_ACCESS_COOKIE_NAME'] = 'new_access_cookie' + app.config['JWT_REFRESH_COOKIE_NAME'] = 'new_refresh_cookie' + app.config['JWT_ACCESS_COOKIE_PATH'] = '/access/path' + app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh/path' + app.config['JWT_COOKIE_SECURE'] = True + app.config['JWT_COOKIE_DOMAIN'] = ".example.com" + app.config['JWT_SESSION_COOKIE'] = False + + app.config['JWT_COOKIE_CSRF_PROTECT'] = True + app.config['JWT_CSRF_METHODS'] = ['GET'] + app.config['JWT_CSRF_IN_COOKIES'] = False + app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'access_csrf_cookie' + app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'refresh_csrf_cookie' + app.config['JWT_ACCESS_CSRF_COOKIE_PATH'] = '/csrf/access/path' + app.config['JWT_REFRESH_CSRF_COOKIE_PATH'] = '/csrf/refresh/path' + app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'X-ACCESS-CSRF' + app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'X-REFRESH-CSRF' + + app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5) + app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=5) + app.config['JWT_ALGORITHM'] = 'HS512' + + app.config['JWT_BLACKLIST_ENABLED'] = True + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' + + app.config['JWT_IDENTITY_CLAIM'] = 'foo' + app.config['JWT_USER_CLAIMS'] = 'bar' + + with app.test_request_context(): + assert config.token_location == ['cookies'] + assert config.jwt_in_cookies is True + assert config.jwt_in_headers is False + assert config.header_name == 'TestHeader' + assert config.header_type == 'TestType' + + assert config.access_cookie_name == 'new_access_cookie' + assert config.refresh_cookie_name == 'new_refresh_cookie' + assert config.access_cookie_path == '/access/path' + assert config.refresh_cookie_path == '/refresh/path' + assert config.cookie_secure is True + assert config.cookie_domain == ".example.com" + assert config.session_cookie is False + + assert config.csrf_protect is True + assert config.csrf_request_methods == ['GET'] + assert config.csrf_in_cookies is False + assert config.access_csrf_cookie_name == 'access_csrf_cookie' + assert config.refresh_csrf_cookie_name == 'refresh_csrf_cookie' + assert config.access_csrf_cookie_path == '/csrf/access/path' + assert config.refresh_csrf_cookie_path == '/csrf/refresh/path' + assert config.access_csrf_header_name == 'X-ACCESS-CSRF' + assert config.refresh_csrf_header_name == 'X-REFRESH-CSRF' + + assert config.access_expires == timedelta(minutes=5) + assert config.refresh_expires == timedelta(days=5) + assert config.algorithm == 'HS512' + + assert config.blacklist_enabled is True + assert config.blacklist_checks == ['refresh'] + assert config.blacklist_access_tokens is False + assert config.blacklist_refresh_tokens is True + + assert config.cookie_max_age == 2147483647 + + assert config.identity_claim == 'foo' + assert config.user_claims == 'bar' + + +# noinspection PyStatementEffect +def test_symmetric_secret_key(app): + with app.test_request_context(): + assert config.is_asymmetric is False + + with pytest.raises(RuntimeError): + config.encode_key + with pytest.raises(RuntimeError): + config.decode_key + + app.secret_key = 'foobar' + with app.test_request_context(): + assert config.encode_key == 'foobar' + assert config.decode_key == 'foobar' + + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + with app.test_request_context(): + assert config.encode_key == 'foobarbaz' + assert config.decode_key == 'foobarbaz' + + +# noinspection PyStatementEffect +def test_default_with_asymmetric_secret_key(app): + with app.test_request_context(): + app.config['JWT_ALGORITHM'] = 'RS256' + assert config.is_asymmetric is True + + # If no key is entered, should raise an error + with pytest.raises(RuntimeError): + config.encode_key + with pytest.raises(RuntimeError): + config.decode_key + + # Make sure the secret key isn't being used for asymmetric stuff + app.secret_key = 'foobar' + with pytest.raises(RuntimeError): + config.encode_key + with pytest.raises(RuntimeError): + config.decode_key + + # Make sure the secret key isn't being used for asymmetric stuff + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + with pytest.raises(RuntimeError): + config.encode_key + with pytest.raises(RuntimeError): + config.decode_key + + app.config['JWT_PUBLIC_KEY'] = 'foo2' + app.config['JWT_PRIVATE_KEY'] = 'bar2' + app.config['JWT_ALGORITHM'] = 'RS256' + with app.test_request_context(): + assert config.decode_key == 'foo2' + assert config.encode_key == 'bar2' + + +# noinspection PyStatementEffect +def test_invalid_config_options(app): + with app.test_request_context(): + app.config['JWT_TOKEN_LOCATION'] = 'banana' + with pytest.raises(RuntimeError): + config.token_location + + app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies', 'banana'] + with pytest.raises(RuntimeError): + config.token_location + + app.config['JWT_HEADER_NAME'] = '' + with app.test_request_context(): + with pytest.raises(RuntimeError): + config.header_name + + app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 'banana' + with pytest.raises(RuntimeError): + config.access_expires + + app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 'banana' + with pytest.raises(RuntimeError): + config.refresh_expires + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana' + with pytest.raises(RuntimeError): + config.blacklist_checks + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'banana'] + with pytest.raises(RuntimeError): + config.blacklist_checks + + +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'] + + +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'] + + +def test_csrf_protect_config(app): + with app.test_request_context(): + app.config['JWT_TOKEN_LOCATION'] = ['headers'] + app.config['JWT_COOKIE_CSRF_PROTECT'] = True + assert config.csrf_protect is False + + app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + app.config['JWT_COOKIE_CSRF_PROTECT'] = True + assert config.csrf_protect is True + + app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + app.config['JWT_COOKIE_CSRF_PROTECT'] = False + assert config.csrf_protect is False + + +def test_depreciated_options(app): + app.config['JWT_CSRF_HEADER_NAME'] = 'Auth' + + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Verify our warnings are thrown + with app.test_request_context(): + with warnings.catch_warnings(record=True) as w: + assert config.access_csrf_header_name == 'Auth' + assert config.refresh_csrf_header_name == 'Auth' + assert len(w) == 2 + assert w[0].category == DeprecationWarning + assert w[1].category == DeprecationWarning diff --git a/tests/test_user_claims_loader.py b/tests/test_user_claims_loader.py new file mode 100644 index 00000000..0e24d7bd --- /dev/null +++ b/tests/test_user_claims_loader.py @@ -0,0 +1,104 @@ +import pytest +from flask import Flask, json, jsonify + +from flask_jwt_extended import ( + JWTManager, create_access_token, jwt_required, get_jwt_claims, + decode_token +) +from tests.utils import get_jwt_manager, make_headers + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + JWTManager(app) + + @app.route('/protected', methods=['GET']) + @jwt_required + def get_claims(): + return jsonify(get_jwt_claims()) + + return app + + +def test_user_claim_in_access_token(app): + jwt = get_jwt_manager(app) + + @jwt.user_claims_loader + def add_claims(identity): + return {'foo': 'bar'} + + with app.test_request_context(): + access_token = create_access_token('username') + + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +def test_non_serializable_user_claims(app): + jwt = get_jwt_manager(app) + + @jwt.user_claims_loader + def add_claims(identity): + return app + + with pytest.raises(TypeError): + with app.test_request_context(): + create_access_token('username') + + +def test_token_from_complex_object(app): + class TestObject: + def __init__(self, username): + self.username = username + + jwt = get_jwt_manager(app) + + @jwt.user_claims_loader + def add_claims(test_obj): + return {'username': test_obj.username} + + @jwt.user_identity_loader + def add_claims(test_obj): + return test_obj.username + + with app.test_request_context(): + access_token = create_access_token(TestObject('username')) + + # Make sure the changes appear in the token + decoded_token = decode_token(access_token) + assert decoded_token['identity'] == 'username' + assert decoded_token['user_claims'] == {'username': 'username'} + + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'username': 'username'} + assert response.status_code == 200 + + +def test_user_claims_with_different_name(app): + jwt = get_jwt_manager(app) + app.config['JWT_USER_CLAIMS'] = 'banana' + + @jwt.user_claims_loader + def add_claims(identity): + return {'foo': 'bar'} + + with app.test_request_context(): + access_token = create_access_token('username') + + # Make sure the name is actually different in the token + decoded_token = decode_token(access_token) + assert decoded_token['banana'] == {'foo': 'bar'} + + # Make sure the correct data is returned to us from the full call + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 diff --git a/tests/test_user_loader.py b/tests/test_user_loader.py index 33e27ac1..4b3e5267 100644 --- a/tests/test_user_loader.py +++ b/tests/test_user_loader.py @@ -1,135 +1,85 @@ -import json -import unittest -from datetime import timedelta - -from flask import Flask, jsonify, request +import pytest +from flask import Flask, jsonify, json from flask_jwt_extended import ( - JWTManager, create_access_token, create_refresh_token, - jwt_refresh_token_required, jwt_required, fresh_jwt_required, - jwt_optional, current_user + JWTManager, jwt_required, current_user, get_current_user, + create_access_token ) +from tests.utils import get_jwt_manager, make_headers + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + JWTManager(app) + + @app.route('/get_user1', methods=['GET']) + @jwt_required + def get_user1(): + return jsonify(foo=get_current_user()['username']) + + @app.route('/get_user2', methods=['GET']) + @jwt_required + def get_user2(): + return jsonify(foo=current_user['username']) + + return app + + +@pytest.mark.parametrize("url", ['/get_user1', '/get_user2']) +def test_load_valid_user(app, url): + jwt = get_jwt_manager(app) + + @jwt.user_loader_callback_loader + def user_load_callback(identity): + return {'username': identity} + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'username'} + + +@pytest.mark.parametrize("url", ['/get_user1', '/get_user2']) +def test_load_invalid_user(app, url): + jwt = get_jwt_manager(app) + + @jwt.user_loader_callback_loader + def user_load_callback(identity): + return None + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': "Error loading the user username"} + + +@pytest.mark.parametrize("url", ['/get_user1', '/get_user2']) +def test_custom_user_loader_errors(app, url): + jwt = get_jwt_manager(app) + + @jwt.user_loader_callback_loader + def user_load_callback(identity): + return None + + @jwt.user_loader_error_loader + def user_loader_error(identity): + return jsonify(foo='bar'), 201 + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') -class TestUserLoader(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.jwt_manager.user_loader_callback_loader - def user_loader(identity): - if identity == 'foobar': - return None - return identity - - @self.app.route('/auth/login', methods=['POST']) - def login(): - username = request.get_json()['username'] - ret = { - 'access_token': create_access_token(username, fresh=True), - 'refresh_token': create_refresh_token(username) - } - return jsonify(ret), 200 - - @self.app.route('/refresh-protected') - @jwt_refresh_token_required - def refresh_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/protected') - @jwt_required - def protected_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/fresh-protected') - @fresh_jwt_required - def fresh_protected_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/partially-protected') - @jwt_optional - def optional_endpoint(): - return jsonify({'username': str(current_user)}) - - def _jwt_get(self, url, jwt): - response = self.client.get(url, content_type='application/json', - headers={'Authorization': 'Bearer {}'.format(jwt)}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_user_loads(self): - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'test'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - def test_failed_user_loads(self): - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'foobar'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - def test_custom_error_callback(self): - @self.jwt_manager.user_loader_error_loader - def custom_user_loader_error_callback(identity): - return jsonify({"msg": "Not found"}), 404 - - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'foobar'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'foo': "bar"} diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index e69de29b..56037c82 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -0,0 +1,179 @@ +import pytest +from datetime import timedelta +from flask import Flask, jsonify, json + +from flask_jwt_extended import ( + jwt_required, fresh_jwt_required, JWTManager, jwt_refresh_token_required, + jwt_optional, create_access_token, create_refresh_token, get_jwt_identity +) +from tests.utils import make_headers + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + JWTManager(app) + + @app.route('/protected', methods=['GET']) + @jwt_required + def protected(): + return jsonify(foo='bar') + + @app.route('/fresh_protected', methods=['GET']) + @fresh_jwt_required + def fresh_protected(): + return jsonify(foo='bar') + + @app.route('/refresh_protected', methods=['GET']) + @jwt_refresh_token_required + def refresh_protected(): + return jsonify(foo='bar') + + @app.route('/optional_protected', methods=['GET']) + @jwt_optional + def optional_protected(): + if get_jwt_identity(): + return jsonify(foo='baz') + else: + return jsonify(foo='bar') + + return app + + +def test_jwt_required(app): + url = '/protected' + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + fresh_access_token = create_access_token('username', fresh=True) + refresh_token = create_refresh_token('username') + + # Access and fresh access should be able to access this + for token in (access_token, fresh_access_token): + response = test_client.get(url, headers=make_headers(token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + # Test accessing jwt_required with no jwt in the request + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing Authorization Header'} + + # Test refresh token access to jwt_required + 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'} + + +def test_fresh_jwt_required(app): + url = '/fresh_protected' + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + fresh_access_token = create_access_token('username', fresh=True) + refresh_token = create_refresh_token('username') + + 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 == 200 + assert json_data == {'foo': 'bar'} + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Fresh token required'} + + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing Authorization Header'} + + 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'} + + +def test_refresh_jwt_required(app): + url = '/refresh_protected' + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + fresh_access_token = create_access_token('username', fresh=True) + refresh_token = create_refresh_token('username') + + 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'} + + 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'} + + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing Authorization Header'} + + response = test_client.get(url, headers=make_headers(refresh_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +def test_jwt_optional(app): + url = '/optional_protected' + + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + fresh_access_token = create_access_token('username', fresh=True) + expired_token = create_access_token('username', expires_delta=timedelta(minutes=-1)) + refresh_token = create_refresh_token('username') + + 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 == 200 + assert json_data == {'foo': 'baz'} + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'baz'} + + 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'} + + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + response = test_client.get(url, headers=make_headers(expired_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Token has expired'} + +# test_jwt_manager +# test_blacklisting +# test_csrf_protection +# test_complex_objects_from_token +# test_token_from_complex_objects +# test_fresh_jwt_required +# test different header name +# test different header type +# test cookies without csrf +# test cookies with csrf +# test asymmetric crypto +# test different name for user claims key in dict diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..9c390a2c --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,6 @@ +def get_jwt_manager(app): + return app.extensions['flask-jwt-extended'] + + +def make_headers(jwt): + return {'Authorization': 'Bearer {}'.format(jwt)} diff --git a/tests_old/app.py b/tests_old/app.py new file mode 100644 index 00000000..c736b74f --- /dev/null +++ b/tests_old/app.py @@ -0,0 +1,464 @@ +import pytest +from datetime import timedelta +from flask import Flask, jsonify, json +from werkzeug.http import parse_cookie + +from flask_jwt_extended import ( + JWTManager, create_access_token, create_refresh_token, jwt_required, + jwt_refresh_token_required, fresh_jwt_required, jwt_optional, + get_current_user, set_access_cookies +) + +RSA_PRIVATE = """ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP +DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo +xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB +AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 +NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D +G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H +bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ +Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ +tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 +cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA +mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv +lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d +E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== +-----END RSA PRIVATE KEY----- +""" + +RSA_PUBLIC = """ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd +E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 +mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= +-----END RSA PUBLIC KEY----- +""" + + +def cartesian_product_general_configs(): + jwt_identity_claims = ['identity', 'sub'] + + configs = [] + for identity in jwt_identity_claims: + configs.append({ + 'JWT_SECRET_KEY': 'testing_secret_key', + 'JWT_ALGORITHM': 'HS256', + 'JWT_IDENTITY_CLAIM': identity + }) + configs.append({ + 'JWT_PUBLIC_KEY': RSA_PUBLIC, + 'JWT_PRIVATE_KEY': RSA_PRIVATE, + 'JWT_ALGORITHM': 'RS256', + 'JWT_IDENTITY_CLAIM': identity + }) + return configs + + +def cartesian_product_header_configs(): + token_locations = ['headers', ['cookies', 'headers']] + header_names = ['Authorization', 'Foo'] + header_types = ['Bearer', 'JWT', ''] + + configs = [] + for location in token_locations: + for header_name in header_names: + for header_type in header_types: + config_combination = { + 'JWT_TOKEN_LOCATION': location, + 'JWT_HEADER_NAME': header_name, + 'JWT_HEADER_TYPE': header_type + } + configs.append(config_combination) + return configs + + +def cartesian_product_cookie_configs(): + token_locations = [['cookies'], ['cookies', 'headers']] + access_cookie_names = ['access_token_cookie', 'access_foo'] + refresh_cookie_names = ['refresh_token_cookie', 'refresh_foo'] + + configs = [] + for location in token_locations: + for access_name in access_cookie_names: + for refresh_name in refresh_cookie_names: + config_combination = { + 'JWT_TOKEN_LOCATION': location, + 'JWT_ACCESS_COOKIE_NAME': access_name, + 'JWT_REFRESH_COOKIE_NAME': refresh_name + } + configs.append(config_combination) + return configs + + +COOKIE_COMBINATIONS = cartesian_product_cookie_configs() +HEADER_COMBINATIONS = cartesian_product_header_configs() +CONFIG_COMBINATIONS = cartesian_product_general_configs() + + +@pytest.fixture(scope='function', params=CONFIG_COMBINATIONS) +def app(request): + app = Flask(__name__) + + for key, value in request.param.items(): + app.config[key] = value + + JWTManager(app) + + @app.route('/fresh_access_jwt', methods=['POST']) + def fresh_access_jwt(): + access_token = create_access_token('username', fresh=True) + return jsonify(jwt=access_token) + + @app.route('/cookie_fresh_access_jwt', methods=['POST']) + def cookie_fresh_access_jwt(): + access_token = create_access_token('username', fresh=True) + resp = jsonify(success=True) + set_access_cookies(resp, access_token) + return resp + + @app.route('/not_fresh_access_jwt', methods=['POST']) + def not_fresh_access_jwt(): + access_token = create_access_token('username', fresh=False) + return jsonify(jwt=access_token) + + @app.route('/custom_expires_access_jwt', methods=['POST']) + def custom_expires_access(): + expires = timedelta(minutes=5) + access_token = create_access_token('username', expires_delta=expires) + return jsonify(jwt=access_token) + + @app.route('/refresh_jwt', methods=['POST']) + def refresh_jwt(): + refresh_token = create_refresh_token('username') + return jsonify(jwt=refresh_token) + + @app.route('/custom_expires_refresh_jwt', methods=['POST']) + def custom_expires_refresh_jwt(): + expires = timedelta(minutes=5) + refresh_token = create_refresh_token('username', expires_delta=expires) + return jsonify(jwt=refresh_token) + + @app.route('/protected', methods=['GET', 'POST']) + @jwt_required + def protected(): + return jsonify(foo='bar') + + @app.route('/fresh_protected', methods=['GET', 'POST']) + @fresh_jwt_required + def fresh_protected(): + return jsonify(foo='bar') + + @app.route('/refresh_protected', methods=['GET', 'POST']) + @jwt_refresh_token_required + def refresh_protected(): + return jsonify(foo='bar') + + @app.route('/optional_protected', methods=['GET', 'POST']) + @jwt_optional + def optional_protected(): + if get_current_user(): + return jsonify(foo='baz') + else: + return jsonify(foo='bar') + + @app.route('/not_protected', methods=['GET', 'POST']) + def not_protected(): + return jsonify(foo='bar') + + return app + + +@pytest.fixture(scope='function', params=HEADER_COMBINATIONS) +def headers_app(request, app): + for key, value in request.param.items(): + app.config[key] = value + return app + + +@pytest.fixture(scope='function', params=COOKIE_COMBINATIONS) +def cookies_app(request, app): + for key, value in request.param.items(): + app.config[key] = value + return app + + +def get_fresh_jwt(test_client): + response = test_client.post('/fresh_access_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + +def get_non_fresh_jwt(test_client): + response = test_client.post('/not_fresh_access_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + +def get_refresh_jwt(test_client): + response = test_client.post('/refresh_jwt') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert 'jwt' in json_data + return json_data['jwt'] + + +def get_cookie_fresh_jwt(test_client): + app = test_client.application + access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + + response = test_client.post('/cookie_fresh_access_jwt') + assert response.status_code == 200 + + cookies = response.headers.getlist('Set-Cookie') + for cookie in cookies: + parsed_cookie = parse_cookie(cookie) + for c_key, c_val in parsed_cookie.items(): + if c_key == access_cookie_name: + return c_val + raise Exception('jwt cooke value not found') + + +def make_request(test_client, request_type, request_url, headers=None, cookies=None): + if cookies is None: + cookies = {} + if cookies: + for c_key, c_val in cookies.items(): + test_client.set_cookie('/', c_key, c_val) + + request_type = getattr(test_client, request_type.lower()) + return request_type( + request_url, + content_type='application/json', + headers=headers + ) + + +def make_jwt_headers_request(test_client, jwt, request_type, request_url): + app = test_client.application + header_name = app.config['JWT_HEADER_NAME'] + header_type = app.config['JWT_HEADER_TYPE'] + headers = {header_name: '{} {}'.format(header_type, jwt).strip()} + return make_request(test_client, request_type, request_url, headers=headers) + + +def make_jwt_cookies_request(test_client, jwt, request_type, request_url): + app = test_client.application + cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] + cookies = {cookie_name: jwt} + return make_request(test_client, request_type, request_url, cookies=cookies) + + +@pytest.mark.parametrize("fail_endpoint", [ + '/protected', + '/fresh_protected', + '/refresh_protected', +]) +@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) +def test_blocked_endpoints_without_jwt(app, fail_endpoint, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + test_client = app.test_client() + response = make_request(test_client, 'GET', fail_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + expected_errors = ( + {'msg': 'Missing Authorization Header'}, + {'msg': 'Missing cookie "access_token_cookie"'}, + {'msg': 'Missing cookie "refresh_token_cookie"'}, + {'msg': 'Missing JWT in headers and cookies'}, + ) + assert response.status_code == 401 + assert json_data in expected_errors + + +@pytest.mark.parametrize("success_endpoint", [ + '/optional_protected', + '/not_protected', +]) +@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) +def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + test_client = app.test_client() + response = make_request(test_client, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/fresh_protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + fresh_jwt = get_fresh_jwt(test_client) + response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) +def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + fresh_jwt = get_fresh_jwt(test_client) + response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) + 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'} + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + jwt = get_non_fresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", [ + '/refresh_protected', + '/fresh_protected' +]) +def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + jwt = get_non_fresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + expected_errors = ( + (422, {'msg': 'Only refresh tokens can access this endpoint'}), + (401, {'msg': 'Fresh token required'}) + ) + assert (response.status_code, json_data) in expected_errors + + +@pytest.mark.parametrize("success_endpoint", [ + '/refresh_protected', + '/not_protected' +]) +def test_accessable_endpoints_with_refresh_jwt_in_headers(headers_app, success_endpoint): + test_client = headers_app.test_client() + jwt = get_refresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("failure_endpoint", [ + '/fresh_protected', + '/protected', + '/optional_protected' +]) +def test_blocked_endpoints_with_refresh_jwt_in_headers(headers_app, failure_endpoint): + test_client = headers_app.test_client() + jwt = get_refresh_jwt(test_client) + response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) + 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'} + + +@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) +def test_bad_header_name_blocks_protected_endpoints(app, token_location): + app.config['JWT_TOKEN_LOCATION'] = token_location + app.config['JWT_HEADER_NAME'] = 'Foo' + + test_client = app.test_client() + jwt = get_fresh_jwt(test_client) + + headers = {'Authorization': 'Bearer {}'.format(jwt)} + response = make_request(test_client, 'GET', '/protected', headers=headers) + json_data = json.loads(response.get_data(as_text=True)) + + expected_json = ( + {'msg': 'Missing Foo Header'}, + {'msg': 'Missing JWT in headers and cookies'} + ) + assert response.status_code == 401 + assert json_data in expected_json + + +@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) +@pytest.mark.parametrize("header_type", ['Foo', '']) +def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_type): + app.config['JWT_TOKEN_LOCATION'] = token_location + app.config['JWT_HEADER_TYPE'] = header_type + + test_client = app.test_client() + jwt = get_fresh_jwt(test_client) + + headers = {'Authorization': 'Bearer {}'.format(jwt)} + response = make_request(test_client, 'GET', '/protected', headers=headers) + json_data = json.loads(response.get_data(as_text=True)) + + expected_json = ( + {'msg': "Bad Authorization header. Expected value ''"}, + {'msg': "Bad Authorization header. Expected value 'Foo '"} + ) + + assert response.status_code == 422 + assert json_data in expected_json + + +@pytest.mark.parametrize("success_endpoint", [ + '/protected', + '/fresh_protected', + '/optional_protected', + '/not_protected', +]) +def test_accessable_endpoints_with_fresh_jwt_in_cookies(cookies_app, success_endpoint): + test_client = cookies_app.test_client() + fresh_jwt = get_cookie_fresh_jwt(test_client) + response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) + json_data = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + +# TODO test sending in headers when cookie_locations and vice versa +# TODO when using cookies with csrf, test GET and POST requests +# TODO test that verifies the jwt identity claim actually changes (sub/identity) +# TODO test possible combinations for jwt_optional +# TODO simple test that the other cookie overrides are working + + +# Various options we want to test stuff here (with different expectations for +# success or failure) +# - JWT in cookies and pass in with cookies +# - JWT in cookies and pass in with headers +# - JWT in headers and pass in with cookies +# - JWT in headers and pass in with headers +# - JWT in headers and cookies and pass in with cookies +# - JWT in headers and cookies and pass in with headers +# +# Everything we want to actually test with the above configurations: +# - all protected endpoints with expected jwts +# - all protected endpoints with unexpected jwts +# - all protected endpoints with expired jwts +# - all protected endpoints with tampered with jwts +# - all protected endpoints with tampered with no jwts +# - all protected endpoints with tampered with no revoked jts +# - all protected endpoints with tampered with no user loader from jwts +# - all protected endpoints with tampered with no no user loader from jwts +# - all protected endpoints with tampered with verified claims in jwts +# - all protected endpoints with tampered with failed verified claims in jwts diff --git a/tests/test_blacklist.py b/tests_old/test_blacklist.py similarity index 100% rename from tests/test_blacklist.py rename to tests_old/test_blacklist.py diff --git a/tests_old/test_config.py b/tests_old/test_config.py new file mode 100644 index 00000000..988bad79 --- /dev/null +++ b/tests_old/test_config.py @@ -0,0 +1,229 @@ +import unittest +import warnings +from datetime import timedelta + +from flask import Flask + +from flask_jwt_extended.config import config +from flask_jwt_extended import JWTManager + + +class TestEndpoints(unittest.TestCase): + + def setUp(self): + self.app = Flask(__name__) + self.app.secret_key = 'super=secret' + JWTManager(self.app) + + def test_default_configs(self): + with self.app.test_request_context(): + self.assertEqual(config.token_location, ['headers']) + self.assertEqual(config.jwt_in_cookies, False) + self.assertEqual(config.jwt_in_headers, True) + self.assertEqual(config.header_name, 'Authorization') + self.assertEqual(config.header_type, 'Bearer') + + self.assertEqual(config.access_cookie_name, 'access_token_cookie') + self.assertEqual(config.refresh_cookie_name, 'refresh_token_cookie') + self.assertEqual(config.access_cookie_path, '/') + self.assertEqual(config.refresh_cookie_path, '/') + self.assertEqual(config.cookie_secure, False) + self.assertEqual(config.cookie_domain, None) + self.assertEqual(config.session_cookie, True) + + self.assertEqual(config.csrf_protect, False) + self.assertEqual(config.csrf_request_methods, ['POST', 'PUT', 'PATCH', 'DELETE']) + self.assertEqual(config.csrf_in_cookies, True) + self.assertEqual(config.access_csrf_cookie_name, 'csrf_access_token') + self.assertEqual(config.refresh_csrf_cookie_name, 'csrf_refresh_token') + self.assertEqual(config.access_csrf_cookie_path, '/') + self.assertEqual(config.refresh_csrf_cookie_path, '/') + self.assertEqual(config.access_csrf_header_name, 'X-CSRF-TOKEN') + self.assertEqual(config.refresh_csrf_header_name, 'X-CSRF-TOKEN') + + self.assertEqual(config.access_expires, timedelta(minutes=15)) + self.assertEqual(config.refresh_expires, timedelta(days=30)) + self.assertEqual(config.algorithm, 'HS256') + self.assertEqual(config.is_asymmetric, False) + self.assertEqual(config.blacklist_enabled, False) + self.assertEqual(config.blacklist_checks, ['access', 'refresh']) + self.assertEqual(config.blacklist_access_tokens, True) + self.assertEqual(config.blacklist_refresh_tokens, True) + + self.assertEqual(config.encode_key, self.app.secret_key) + self.assertEqual(config.decode_key, self.app.secret_key) + self.assertEqual(config.cookie_max_age, None) + + self.assertEqual(config.identity_claim, 'identity') + self.assertEqual(config.user_claims, 'user_claims') + + def test_override_configs(self): + self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + self.app.config['JWT_HEADER_NAME'] = 'TestHeader' + self.app.config['JWT_HEADER_TYPE'] = 'TestType' + + self.app.config['JWT_ACCESS_COOKIE_NAME'] = 'new_access_cookie' + self.app.config['JWT_REFRESH_COOKIE_NAME'] = 'new_refresh_cookie' + self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/access/path' + self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh/path' + self.app.config['JWT_COOKIE_SECURE'] = True + self.app.config['JWT_COOKIE_DOMAIN'] = ".example.com" + self.app.config['JWT_SESSION_COOKIE'] = False + + self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True + self.app.config['JWT_CSRF_METHODS'] = ['GET'] + self.app.config['JWT_CSRF_IN_COOKIES'] = False + self.app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'access_csrf_cookie' + self.app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'refresh_csrf_cookie' + self.app.config['JWT_ACCESS_CSRF_COOKIE_PATH'] = '/csrf/access/path' + self.app.config['JWT_REFRESH_CSRF_COOKIE_PATH'] = '/csrf/refresh/path' + self.app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'X-ACCESS-CSRF' + self.app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'X-REFRESH-CSRF' + + self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5) + self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=5) + self.app.config['JWT_ALGORITHM'] = 'HS512' + + self.app.config['JWT_BLACKLIST_ENABLED'] = True + self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' + + self.app.secret_key = 'banana' + + self.app.config['JWT_IDENTITY_CLAIM'] = 'foo' + self.app.config['JWT_USER_CLAIMS'] = 'bar' + + with self.app.test_request_context(): + self.assertEqual(config.token_location, ['cookies']) + self.assertEqual(config.jwt_in_cookies, True) + self.assertEqual(config.jwt_in_headers, False) + self.assertEqual(config.header_name, 'TestHeader') + self.assertEqual(config.header_type, 'TestType') + + self.assertEqual(config.access_cookie_name, 'new_access_cookie') + self.assertEqual(config.refresh_cookie_name, 'new_refresh_cookie') + self.assertEqual(config.access_cookie_path, '/access/path') + self.assertEqual(config.refresh_cookie_path, '/refresh/path') + self.assertEqual(config.cookie_secure, True) + self.assertEqual(config.cookie_domain, ".example.com") + self.assertEqual(config.session_cookie, False) + + self.assertEqual(config.csrf_protect, True) + self.assertEqual(config.csrf_request_methods, ['GET']) + self.assertEqual(config.csrf_in_cookies, False) + self.assertEqual(config.access_csrf_cookie_name, 'access_csrf_cookie') + self.assertEqual(config.refresh_csrf_cookie_name, 'refresh_csrf_cookie') + self.assertEqual(config.access_csrf_cookie_path, '/csrf/access/path') + self.assertEqual(config.refresh_csrf_cookie_path, '/csrf/refresh/path') + self.assertEqual(config.access_csrf_header_name, 'X-ACCESS-CSRF') + self.assertEqual(config.refresh_csrf_header_name, 'X-REFRESH-CSRF') + + self.assertEqual(config.access_expires, timedelta(minutes=5)) + self.assertEqual(config.refresh_expires, timedelta(days=5)) + self.assertEqual(config.algorithm, 'HS512') + + self.assertEqual(config.blacklist_enabled, True) + self.assertEqual(config.blacklist_checks, ['refresh']) + self.assertEqual(config.blacklist_access_tokens, False) + self.assertEqual(config.blacklist_refresh_tokens, True) + + self.assertEqual(config.cookie_max_age, 2147483647) + + self.assertEqual(config.identity_claim, 'foo') + self.assertEqual(config.user_claims, 'bar') + + def test_invalid_config_options(self): + with self.app.test_request_context(): + self.app.config['JWT_TOKEN_LOCATION'] = 'banana' + with self.assertRaises(RuntimeError): + config.token_location + + self.app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies', 'banana'] + with self.assertRaises(RuntimeError): + config.token_location + + self.app.config['JWT_HEADER_NAME'] = '' + with self.app.test_request_context(): + with self.assertRaises(RuntimeError): + config.header_name + + self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 'banana' + with self.assertRaises(RuntimeError): + config.access_expires + + self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 'banana' + with self.assertRaises(RuntimeError): + config.refresh_expires + + self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana' + with self.assertRaises(RuntimeError): + config.blacklist_checks + + self.app.secret_key = None + with self.assertRaises(RuntimeError): + config.decode_key + + self.app.secret_key = '' + with self.assertRaises(RuntimeError): + config.decode_key + + self.app.secret_key = None + with self.assertRaises(RuntimeError): + config.decode_key + + self.app.config['JWT_ALGORITHM'] = 'RS256' + self.app.config['JWT_PUBLIC_KEY'] = None + self.app.config['JWT_PRIVATE_KEY'] = None + with self.assertRaises(RuntimeError): + config.decode_key + with self.assertRaises(RuntimeError): + config.encode_key + + def test_depreciated_options(self): + self.app.config['JWT_CSRF_HEADER_NAME'] = 'Auth' + + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Verify our warnings are thrown + with self.app.test_request_context(): + with warnings.catch_warnings(record=True) as w: + self.assertEqual(config.access_csrf_header_name, 'Auth') + self.assertEqual(config.refresh_csrf_header_name, 'Auth') + self.assertEqual(len(w), 2) + self.assertEqual(w[0].category, DeprecationWarning) + self.assertEqual(w[1].category, DeprecationWarning) + + def test_special_config_options(self): + with self.app.test_request_context(): + # Test changing strings to lists for JWT_TOKEN_LOCATIONS + self.app.config['JWT_TOKEN_LOCATION'] = 'headers' + self.assertEqual(config.token_location, ['headers']) + self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] + self.assertEqual(config.token_location, ['headers']) + self.app.config['JWT_TOKEN_LOCATION'] = 'cookies' + self.assertEqual(config.token_location, ['cookies']) + self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + self.assertEqual(config.token_location, ['cookies']) + self.app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] + self.assertEqual(config.token_location, ['cookies', 'headers']) + + # Test csrf protect options + self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] + self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True + self.assertEqual(config.csrf_protect, False) + self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True + self.assertEqual(config.csrf_protect, True) + self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False + self.assertEqual(config.csrf_protect, False) + + def test_asymmetric_encryption_key_handling(self): + self.app.config['JWT_PRIVATE_KEY'] = 'MOCK_RSA_PRIVATE_KEY' + self.app.config['JWT_PUBLIC_KEY'] = 'MOCK_RSA_PUBLIC_KEY' + self.app.config['JWT_ALGORITHM'] = 'RS256' + + with self.app.test_request_context(): + self.assertEqual(config.is_asymmetric, True) + self.assertEqual(config.encode_key, 'MOCK_RSA_PRIVATE_KEY') + self.assertEqual(config.decode_key, 'MOCK_RSA_PUBLIC_KEY') diff --git a/tests/test_jwt_encode_decode.py b/tests_old/test_jwt_encode_decode.py similarity index 100% rename from tests/test_jwt_encode_decode.py rename to tests_old/test_jwt_encode_decode.py diff --git a/tests/test_jwt_manager.py b/tests_old/test_jwt_manager.py similarity index 100% rename from tests/test_jwt_manager.py rename to tests_old/test_jwt_manager.py diff --git a/tests/test_protected_endpoints.py b/tests_old/test_protected_endpoints.py similarity index 100% rename from tests/test_protected_endpoints.py rename to tests_old/test_protected_endpoints.py diff --git a/tests/test_user_claims_verification.py b/tests_old/test_user_claims_verification.py similarity index 100% rename from tests/test_user_claims_verification.py rename to tests_old/test_user_claims_verification.py diff --git a/tests_old/test_user_loader.py b/tests_old/test_user_loader.py new file mode 100644 index 00000000..33e27ac1 --- /dev/null +++ b/tests_old/test_user_loader.py @@ -0,0 +1,135 @@ +import json +import unittest +from datetime import timedelta + +from flask import Flask, jsonify, request + +from flask_jwt_extended import ( + JWTManager, create_access_token, create_refresh_token, + jwt_refresh_token_required, jwt_required, fresh_jwt_required, + jwt_optional, current_user +) + + +class TestUserLoader(unittest.TestCase): + + def setUp(self): + self.app = Flask(__name__) + self.app.secret_key = 'super=secret' + self.jwt_manager = JWTManager(self.app) + self.client = self.app.test_client() + + @self.jwt_manager.user_loader_callback_loader + def user_loader(identity): + if identity == 'foobar': + return None + return identity + + @self.app.route('/auth/login', methods=['POST']) + def login(): + username = request.get_json()['username'] + ret = { + 'access_token': create_access_token(username, fresh=True), + 'refresh_token': create_refresh_token(username) + } + return jsonify(ret), 200 + + @self.app.route('/refresh-protected') + @jwt_refresh_token_required + def refresh_endpoint(): + return jsonify({'username': str(current_user)}) + + @self.app.route('/protected') + @jwt_required + def protected_endpoint(): + return jsonify({'username': str(current_user)}) + + @self.app.route('/fresh-protected') + @fresh_jwt_required + def fresh_protected_endpoint(): + return jsonify({'username': str(current_user)}) + + @self.app.route('/partially-protected') + @jwt_optional + def optional_endpoint(): + return jsonify({'username': str(current_user)}) + + def _jwt_get(self, url, jwt): + response = self.client.get(url, content_type='application/json', + headers={'Authorization': 'Bearer {}'.format(jwt)}) + status_code = response.status_code + data = json.loads(response.get_data(as_text=True)) + return status_code, data + + def test_user_loads(self): + response = self.client.post('/auth/login', content_type='application/json', + data=json.dumps({'username': 'test'})) + data = json.loads(response.get_data(as_text=True)) + access_token = data['access_token'] + refresh_token = data['refresh_token'] + + status, data = self._jwt_get('/protected', access_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'username': 'test'}) + + status, data = self._jwt_get('/fresh-protected', access_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'username': 'test'}) + + status, data = self._jwt_get('/partially-protected', access_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'username': 'test'}) + + status, data = self._jwt_get('/refresh-protected', refresh_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'username': 'test'}) + + def test_failed_user_loads(self): + response = self.client.post('/auth/login', content_type='application/json', + data=json.dumps({'username': 'foobar'})) + data = json.loads(response.get_data(as_text=True)) + access_token = data['access_token'] + refresh_token = data['refresh_token'] + + status, data = self._jwt_get('/protected', access_token) + self.assertEqual(status, 401) + self.assertEqual(data, {'msg': 'Error loading the user foobar'}) + + status, data = self._jwt_get('/fresh-protected', access_token) + self.assertEqual(status, 401) + self.assertEqual(data, {'msg': 'Error loading the user foobar'}) + + status, data = self._jwt_get('/partially-protected', access_token) + self.assertEqual(status, 401) + self.assertEqual(data, {'msg': 'Error loading the user foobar'}) + + status, data = self._jwt_get('/refresh-protected', refresh_token) + self.assertEqual(status, 401) + self.assertEqual(data, {'msg': 'Error loading the user foobar'}) + + def test_custom_error_callback(self): + @self.jwt_manager.user_loader_error_loader + def custom_user_loader_error_callback(identity): + return jsonify({"msg": "Not found"}), 404 + + response = self.client.post('/auth/login', content_type='application/json', + data=json.dumps({'username': 'foobar'})) + data = json.loads(response.get_data(as_text=True)) + access_token = data['access_token'] + refresh_token = data['refresh_token'] + + status, data = self._jwt_get('/protected', access_token) + self.assertEqual(status, 404) + self.assertEqual(data, {'msg': 'Not found'}) + + status, data = self._jwt_get('/fresh-protected', access_token) + self.assertEqual(status, 404) + self.assertEqual(data, {'msg': 'Not found'}) + + status, data = self._jwt_get('/partially-protected', access_token) + self.assertEqual(status, 404) + self.assertEqual(data, {'msg': 'Not found'}) + + status, data = self._jwt_get('/refresh-protected', refresh_token) + self.assertEqual(status, 404) + self.assertEqual(data, {'msg': 'Not found'}) diff --git a/tox.ini b/tox.ini index 4e1337d4..a6c27b85 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ envlist = py27, py34, py35, py36 [testenv] commands = - coverage run --source flask_jwt_extended/ -m unittest discover --start-directory tests + coverage run --source flask_jwt_extended -m pytest tests/ coverage report -m deps = coverage From 2ef05801dc8791e65e7a2a5ff1be1a8ad26e711e Mon Sep 17 00:00:00 2001 From: Landon GB Date: Fri, 20 Oct 2017 16:15:23 -0600 Subject: [PATCH 08/18] Token blacklisting tests --- tests/test_blacklist.py | 162 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tests/test_blacklist.py diff --git a/tests/test_blacklist.py b/tests/test_blacklist.py new file mode 100644 index 00000000..8ede97bd --- /dev/null +++ b/tests/test_blacklist.py @@ -0,0 +1,162 @@ +import pytest +from flask import Flask, jsonify, json + +from flask_jwt_extended import ( + JWTManager, jwt_required, create_access_token, + jwt_refresh_token_required, + create_refresh_token +) +from tests.utils import get_jwt_manager, make_headers + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + app.config['JWT_BLACKLIST_ENABLED'] = True + JWTManager(app) + + @app.route('/protected', methods=['GET']) + @jwt_required + def access_protected(): + return jsonify(foo='bar') + + @app.route('/refresh_protected', methods=['GET']) + @jwt_refresh_token_required + def refresh_protected(): + return jsonify(foo='bar') + + return app + + +@pytest.mark.parametrize("blacklist_type", [['access'], ['refresh', 'access']]) +def test_non_blacklisted_access_token(app, blacklist_type): + jwt = get_jwt_manager(app) + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = blacklist_type + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return False + + with app.test_request_context(): + access_token = create_access_token('username') + + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +@pytest.mark.parametrize("blacklist_type", [['access'], ['refresh', 'access']]) +def test_blacklisted_access_token(app, blacklist_type): + jwt = get_jwt_manager(app) + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = blacklist_type + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return True + + with app.test_request_context(): + access_token = create_access_token('username') + + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'msg': 'Token has been revoked'} + assert response.status_code == 401 + + +@pytest.mark.parametrize("blacklist_type", [['refresh'], ['refresh', 'access']]) +def test_non_blacklisted_refresh_token(app, blacklist_type): + jwt = get_jwt_manager(app) + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = blacklist_type + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return False + + with app.test_request_context(): + refresh_token = create_refresh_token('username') + + test_client = app.test_client() + response = test_client.get('/refresh_protected', headers=make_headers(refresh_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +@pytest.mark.parametrize("blacklist_type", [['refresh'], ['refresh', 'access']]) +def test_blacklisted_refresh_token(app, blacklist_type): + jwt = get_jwt_manager(app) + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = blacklist_type + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return True + + with app.test_request_context(): + refresh_token = create_refresh_token('username') + + test_client = app.test_client() + response = test_client.get('/refresh_protected', headers=make_headers(refresh_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'msg': 'Token has been revoked'} + assert response.status_code == 401 + + +def test_no_blacklist_callback_method_provided(app): + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access'] + + 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)) + + +def test_revoked_token_of_different_type(app): + jwt = get_jwt_manager(app) + test_client = app.test_client() + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return True + + with app.test_request_context(): + access_token = create_access_token('username') + refresh_token = create_refresh_token('username') + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access'] + response = test_client.get('/refresh_protected', headers=make_headers(refresh_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['refresh'] + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'foo': 'bar'} + assert response.status_code == 200 + + +def test_custom_blacklisted_message(app): + jwt = get_jwt_manager(app) + + @jwt.token_in_blacklist_loader + def check_blacklisted(decrypted_token): + return True + + @jwt.revoked_token_loader + def custom_error(): + return jsonify(baz='foo'), 404 + + with app.test_request_context(): + access_token = create_access_token('username') + + test_client = app.test_client() + response = test_client.get('/protected', headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'baz': 'foo'} + assert response.status_code == 404 From 880bb5fb8d58ca76a69bc3f54ffefd9ad01f6c7d Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 22 Oct 2017 18:34:45 -0600 Subject: [PATCH 09/18] Add pytest for decoding tokens --- tests/test_decode_tokens.py | 112 ++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/test_decode_tokens.py diff --git a/tests/test_decode_tokens.py b/tests/test_decode_tokens.py new file mode 100644 index 00000000..897ff6dd --- /dev/null +++ b/tests/test_decode_tokens.py @@ -0,0 +1,112 @@ +import jwt +import pytest +from datetime import timedelta + +from flask import Flask +from jwt import ExpiredSignatureError + +from flask_jwt_extended import ( + JWTManager, create_access_token, decode_token, create_refresh_token +) +from flask_jwt_extended.config import config +from flask_jwt_extended.exceptions import JWTDecodeError +from tests.utils import get_jwt_manager + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'change_me' + app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] + app.config['JWT_COOKIE_CSRF_PROTECT'] = True + JWTManager(app) + return app + + +@pytest.fixture(scope='function') +def default_access_token(app): + with app.test_request_context(): + return { + 'jti': '1234', + config.identity_claim: 'username', + 'type': 'access', + 'fresh': True, + 'csrf': 'abcd' + } + + +def _encode_token(app, token_data): + with app.test_request_context(): + token = jwt.encode( + token_data, + config.decode_key, + algorithm=config.algorithm + ) + return token.decode('utf-8') + + +@pytest.mark.parametrize("user_loader_return", [{}, None]) +def test_no_user_claims(app, user_loader_return): + jwtM = get_jwt_manager(app) + + @jwtM.user_claims_loader + def empty_user_loader_return(identity): + return user_loader_return + + # Identity should not be in the actual token, but should be in the data + # returned via the decode_token call + with app.test_request_context(): + token = create_access_token('username') + pure_decoded = jwt.decode(token, config.decode_key, algorithms=[config.algorithm]) + assert config.user_claims not in pure_decoded + extension_decoded = decode_token(token) + assert config.user_claims in extension_decoded + + +@pytest.mark.parametrize("missing_claim", ['jti', 'type', 'identity', 'fresh', 'csrf']) +def test_missing_jti_claim(app, default_access_token, missing_claim): + del default_access_token[missing_claim] + missing_jwt_token = _encode_token(app, default_access_token) + + with pytest.raises(JWTDecodeError): + with app.test_request_context(): + decode_token(missing_jwt_token) + + +def test_bad_token_type(app, default_access_token): + default_access_token['type'] = 'banana' + bad_type_token = _encode_token(app, default_access_token) + + with pytest.raises(JWTDecodeError): + with app.test_request_context(): + decode_token(bad_type_token) + + +def test_expired_token(app): + with app.test_request_context(): + delta = timedelta(minutes=-5) + access_token = create_access_token('username', expires_delta=delta) + refresh_token = create_refresh_token('username', expires_delta=delta) + with pytest.raises(ExpiredSignatureError): + decode_token(access_token) + with pytest.raises(ExpiredSignatureError): + decode_token(refresh_token) + + +def test_alternate_identity_claim(app, default_access_token): + app.config['JWT_IDENTITY_CLAIM'] = 'sub' + + # Insure decoding fails if the claim isn't there + token = _encode_token(app, default_access_token) + with pytest.raises(JWTDecodeError): + with app.test_request_context(): + decode_token(token) + + # Insure the claim exists in the decoded jwt + del default_access_token['identity'] + default_access_token['sub'] = 'username' + token = _encode_token(app, default_access_token) + with app.test_request_context(): + decoded = decode_token(token) + assert 'sub' in decoded + assert 'identity' not in decoded From bbbe25d124d4d76e769d5c592a5984555a1f980b Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 22 Oct 2017 20:40:40 -0600 Subject: [PATCH 10/18] Start testing cookies and update todos --- tests/test_cookies.py | 138 ++++++++++++++++++++++++++++++++++ tests/test_decode_tokens.py | 10 ++- tests/test_view_decorators.py | 15 +--- 3 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 tests/test_cookies.py diff --git a/tests/test_cookies.py b/tests/test_cookies.py new file mode 100644 index 00000000..5ed8eb65 --- /dev/null +++ b/tests/test_cookies.py @@ -0,0 +1,138 @@ +import pytest +from flask import Flask, jsonify, json +from werkzeug.http import parse_cookie + +from flask_jwt_extended import ( + jwt_required, JWTManager, jwt_refresh_token_required, create_access_token, + create_refresh_token, set_access_cookies, set_refresh_cookies, + unset_jwt_cookies +) +from flask_jwt_extended.config import config + + +def _get_jwt_from_response_cookie(response, cookie_name): + cookies = response.headers.getlist('Set-Cookie') + for cookie in cookies: + parsed_cookie = parse_cookie(cookie) + for c_key, c_val in parsed_cookie.items(): + if c_key == cookie_name: + return c_val + raise Exception('jwt cooke value not found') + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + app.config['JWT_TOKEN_LOCATION'] = ['cookies'] + JWTManager(app) + + @app.route('/access_token', methods=['GET']) + def access_token(): + resp = jsonify(login=True) + access_token = create_access_token('username') + set_access_cookies(resp, access_token) + return resp + + @app.route('/refresh_token', methods=['GET']) + def refresh_token(): + resp = jsonify(login=True) + refresh_token = create_refresh_token('username') + set_refresh_cookies(resp, refresh_token) + return resp + + @app.route('/delete_tokens', methods=['GET']) + def delete_tokens(): + resp = jsonify(logout=True) + unset_jwt_cookies(resp) + return resp + + @app.route('/protected', methods=['GET']) + @jwt_required + def protected(): + return jsonify(foo='bar') + + @app.route('/refresh_protected', methods=['GET']) + @jwt_refresh_token_required + def refresh_protected(): + return jsonify(foo='bar') + + return app + + +def test_jwt_required_with_valid_cookies(app): + test_client = app.test_client() + + # Test without cookies + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + + # Test after receiving cookies + test_client.get('/access_token') + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + # Test after issuing a 'logout' to delete the cookies + test_client.get('/delete_tokens') + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + + +def test_jwt_refresh_required_with_cookies(app): + test_client = app.test_client() + + # Test without cookies + response = test_client.get('/refresh_protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing cookie "refresh_token_cookie"'} + + # Test after receiving cookies + test_client.get('/refresh_token') + response = test_client.get('/refresh_protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + # Test after issuing a 'logout' to delete the cookies + test_client.get('/delete_tokens') + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + + +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') + + +def test_custom_cookie_options(app): + # Test the default options on the received cookies + app.config['JWT_ACCESS_COOKIE_NAME'] = 'access_foo' + app.config['JWT_REFRESH_COOKIE_NAME'] = 'refresh_foo' + app.config['JWT_ACCESS_COOKIE_PATH'] = '/protected' + app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh_protected' + app.config['JWT_COOKIE_SECURE'] = True + app.config['JWT_COOKIE_DOMAIN'] = 'test.com' + app.config['JWT_SESSION_COOKIE'] = False + app.config['JWT_COOKIE_CSRF_PROTECT'] = False + + # Test the updated options on the received cookies + pass + +# TODO test csrf with multi methods +# TODO test different cookie names diff --git a/tests/test_decode_tokens.py b/tests/test_decode_tokens.py index 897ff6dd..08ce6046 100644 --- a/tests/test_decode_tokens.py +++ b/tests/test_decode_tokens.py @@ -6,7 +6,8 @@ from jwt import ExpiredSignatureError from flask_jwt_extended import ( - JWTManager, create_access_token, decode_token, create_refresh_token + JWTManager, create_access_token, decode_token, create_refresh_token, + get_jti ) from flask_jwt_extended.config import config from flask_jwt_extended.exceptions import JWTDecodeError @@ -110,3 +111,10 @@ def test_alternate_identity_claim(app, default_access_token): decoded = decode_token(token) assert 'sub' in decoded assert 'identity' not in decoded + + +def test_get_jti(app, default_access_token): + token = _encode_token(app, default_access_token) + + with app.test_request_context(): + assert default_access_token['jti'] == get_jti(token) diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index 56037c82..cbfb033a 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -165,15 +165,6 @@ def test_jwt_optional(app): assert response.status_code == 401 assert json_data == {'msg': 'Token has expired'} -# test_jwt_manager -# test_blacklisting -# test_csrf_protection -# test_complex_objects_from_token -# test_token_from_complex_objects -# test_fresh_jwt_required -# test different header name -# test different header type -# test cookies without csrf -# test cookies with csrf -# test asymmetric crypto -# test different name for user claims key in dict +# TODO test different header name and type +# TODO test asymmetric crypto +# TODO test different name for user claims key in dict From 08734a12d75d447c5270057069b7b3e4f3a5001d Mon Sep 17 00:00:00 2001 From: Landon GB Date: Sun, 22 Oct 2017 22:49:08 -0600 Subject: [PATCH 11/18] Test cookie options with set_cookies and unset_cookies methods --- tests/test_cookies.py | 169 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 157 insertions(+), 12 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 5ed8eb65..c7f2412a 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -10,14 +10,13 @@ from flask_jwt_extended.config import config -def _get_jwt_from_response_cookie(response, cookie_name): +def _get_cookie_from_response(response, cookie_name): cookies = response.headers.getlist('Set-Cookie') for cookie in cookies: parsed_cookie = parse_cookie(cookie) - for c_key, c_val in parsed_cookie.items(): - if c_key == cookie_name: - return c_val - raise Exception('jwt cooke value not found') + if cookie_name in parsed_cookie: + return parsed_cookie + return None @pytest.fixture(scope='function') @@ -120,19 +119,165 @@ def test_setting_cookies_wihout_cookies_enabled(app): test_client.get('/delete_tokens') +def test_default_cookie_options(app): + test_client = app.test_client() + + # Test the default access cookies + response = test_client.get('/access_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + access_cookie = _get_cookie_from_response(response, 'access_token_cookie') + access_csrf_cookie = _get_cookie_from_response(response, 'csrf_access_token') + assert 'access_token_cookie' in access_cookie + assert access_cookie['HttpOnly; Path'] == '/' + assert 'csrf_access_token' in access_csrf_cookie + + # Test the default refresh cookies + response = test_client.get('/refresh_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') + refresh_csrf_cookie = _get_cookie_from_response(response, 'csrf_refresh_token') + assert 'refresh_token_cookie' in refresh_cookie + assert 'HttpOnly; Path' in refresh_cookie + assert refresh_cookie['HttpOnly; Path'] == '/' + assert 'csrf_refresh_token' in refresh_csrf_cookie + + def test_custom_cookie_options(app): - # Test the default options on the received cookies + test_client = app.test_client() + + app.config['JWT_COOKIE_SECURE'] = True + app.config['JWT_COOKIE_DOMAIN'] = 'test.com' + app.config['JWT_SESSION_COOKIE'] = False + + # Test access cookies with changed options + response = test_client.get('/access_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + + access_cookie = _get_cookie_from_response(response, 'access_token_cookie') + assert 'access_token_cookie' in access_cookie + assert 'Domain' in access_cookie + assert 'Expires=' in str(cookies[0]) # Ignored by parse_cookie :( + assert 'Secure; HttpOnly; Path' in access_cookie + assert access_cookie['Domain'] == 'test.com' + assert access_cookie['Secure; HttpOnly; Path'] == '/' + + access_csrf_cookie = _get_cookie_from_response(response, 'csrf_access_token') + assert 'csrf_access_token' in access_csrf_cookie + assert 'Domain' in access_csrf_cookie + assert 'Expires=' in str(cookies[1]) # Ignored by parse_cookie :( + assert 'Secure; Path' in access_csrf_cookie + assert access_csrf_cookie['Domain'] == 'test.com' + assert access_csrf_cookie['Secure; Path'] == '/' + + # Test refresh cookies with changed options + response = test_client.get('/refresh_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + + refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') + assert 'refresh_token_cookie' in refresh_cookie + assert 'Domain' in refresh_cookie + assert 'Expires=' in str(cookies[0]) # Ignored by parse_cookie :( + assert 'Secure; HttpOnly; Path' in refresh_cookie + assert refresh_cookie['Domain'] == 'test.com' + assert refresh_cookie['Secure; HttpOnly; Path'] == '/' + + refresh_csrf_cookie = _get_cookie_from_response(response, 'csrf_refresh_token') + assert 'csrf_refresh_token' in refresh_csrf_cookie + assert 'Domain' in refresh_csrf_cookie + assert 'Expires=' in str(cookies[1]) # Ignored by parse_cookie :( + assert 'Secure; Path' in refresh_csrf_cookie + assert refresh_csrf_cookie['Domain'] == 'test.com' + assert refresh_csrf_cookie['Secure; Path'] == '/' + + +def test_custom_cookie_names_and_paths(app): + test_client = app.test_client() + + app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'access_foo_csrf' + app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'refresh_foo_csrf' + app.config['JWT_ACCESS_CSRF_COOKIE_PATH'] = '/protected' + app.config['JWT_REFRESH_CSRF_COOKIE_PATH'] = '/refresh_protected' app.config['JWT_ACCESS_COOKIE_NAME'] = 'access_foo' app.config['JWT_REFRESH_COOKIE_NAME'] = 'refresh_foo' app.config['JWT_ACCESS_COOKIE_PATH'] = '/protected' app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh_protected' - app.config['JWT_COOKIE_SECURE'] = True - app.config['JWT_COOKIE_DOMAIN'] = 'test.com' - app.config['JWT_SESSION_COOKIE'] = False + + # Test the default access cookies + response = test_client.get('/access_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + + access_cookie = _get_cookie_from_response(response, 'access_foo') + access_csrf_cookie = _get_cookie_from_response(response, 'access_foo_csrf') + assert 'access_foo' in access_cookie + assert 'access_foo_csrf' in access_csrf_cookie + + # The parse cookie library ignores 'Path' cookies, and we don't know which + # cookie in the list is the csrf cookie and which is the jwt cookie. So + # we have to resort to doing string comparisons on both of them. + assert 'Path=/protected' in cookies[0] + assert 'Path=/protected' in cookies[1] + + # Test the default refresh cookies + response = test_client.get('/refresh_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 2 # JWT and CSRF value + + refresh_cookie = _get_cookie_from_response(response, 'refresh_foo') + refresh_csrf_cookie = _get_cookie_from_response(response, 'refresh_foo_csrf') + assert 'refresh_foo' in refresh_cookie + assert 'refresh_foo_csrf' in refresh_csrf_cookie + + # The parse cookie library ignores 'Path' cookies, and we don't know which + # cookie in the list is the csrf cookie and which is the jwt cookie. So + # we have to resort to doing string comparisons on both of them. + assert 'Path=/refresh_protected' in cookies[0] + assert 'Path=/refresh_protected' in cookies[1] + + +def test_csrf_token_not_in_cookie(app): + test_client = app.test_client() + + app.config['JWT_CSRF_IN_COOKIES'] = False + + # Test the default access cookies + response = test_client.get('/access_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 1 + access_cookie = _get_cookie_from_response(response, 'access_token_cookie') + assert 'access_token_cookie' in access_cookie + + # Test the default refresh cookies + response = test_client.get('/refresh_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 1 + refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') + assert 'refresh_token_cookie' in refresh_cookie + + +def test_cookies_without_csrf(app): + test_client = app.test_client() + app.config['JWT_COOKIE_CSRF_PROTECT'] = False - # Test the updated options on the received cookies - pass + # Test the default access cookies + response = test_client.get('/access_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 1 + access_cookie = _get_cookie_from_response(response, 'access_token_cookie') + assert 'access_token_cookie' in access_cookie + + # Test the default refresh cookies + response = test_client.get('/refresh_token') + cookies = response.headers.getlist('Set-Cookie') + assert len(cookies) == 1 + refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') + assert 'refresh_token_cookie' in refresh_cookie # TODO test csrf with multi methods -# TODO test different cookie names +# TODO test headers +# TODO test cookies and headers together From 91794fa083c504db8484a776979412b2624cea90 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 10:26:44 -0600 Subject: [PATCH 12/18] CSRF unit tests --- tests/test_cookies.py | 130 +++++++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 20 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index c7f2412a..f438e9c2 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -7,7 +7,6 @@ create_refresh_token, set_access_cookies, set_refresh_cookies, unset_jwt_cookies ) -from flask_jwt_extended.config import config def _get_cookie_from_response(response, cookie_name): @@ -51,60 +50,152 @@ def delete_tokens(): def protected(): return jsonify(foo='bar') + @app.route('/post_protected', methods=['POST']) + @jwt_required + def post_protected(): + return jsonify(foo='bar') + @app.route('/refresh_protected', methods=['GET']) @jwt_refresh_token_required def refresh_protected(): return jsonify(foo='bar') + @app.route('/post_refresh_protected', methods=['POST']) + @jwt_refresh_token_required + def post_refresh_protected(): + return jsonify(foo='bar') + return app -def test_jwt_required_with_valid_cookies(app): +@pytest.mark.parametrize("options", [ + ('/refresh_token', 'refresh_token_cookie', '/refresh_protected'), + ('/access_token', 'access_token_cookie', '/protected') +]) +def test_jwt_refresh_required_with_cookies(app, options): test_client = app.test_client() + auth_url, cookie_name, protected_url = options # Test without cookies - response = test_client.get('/protected') + response = test_client.get(protected_url) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 401 - assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + assert json_data == {'msg': 'Missing cookie "{}"'.format(cookie_name)} # Test after receiving cookies - test_client.get('/access_token') - response = test_client.get('/protected') + test_client.get(auth_url) + response = test_client.get(protected_url) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'bar'} # Test after issuing a 'logout' to delete the cookies test_client.get('/delete_tokens') - response = test_client.get('/protected') + response = test_client.get(protected_url) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 401 - assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + assert json_data == {'msg': 'Missing cookie "{}"'.format(cookie_name)} -def test_jwt_refresh_required_with_cookies(app): +@pytest.mark.parametrize("options", [ + ('/refresh_token', 'csrf_refresh_token', '/post_refresh_protected'), + ('/access_token', 'csrf_access_token', '/post_protected') +]) +def test_default_access_csrf_protection(app, options): test_client = app.test_client() + auth_url, csrf_cookie_name, post_url = options - # Test without cookies - response = test_client.get('/refresh_protected') + # Get the jwt cookies and csrf double submit tokens + response = test_client.get(auth_url) + csrf_cookie = _get_cookie_from_response(response, csrf_cookie_name) + csrf_token = csrf_cookie[csrf_cookie_name] + + # Test you cannot post without the additional csrf protection + response = test_client.post(post_url) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 401 - assert json_data == {'msg': 'Missing cookie "refresh_token_cookie"'} + assert json_data == {'msg': 'Missing CSRF token in headers'} - # Test after receiving cookies - test_client.get('/refresh_token') - response = test_client.get('/refresh_protected') + # Test that you can post with the csrf double submit value + csrf_headers = {'X-CSRF-TOKEN': csrf_token} + response = test_client.post(post_url, headers=csrf_headers) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'bar'} - # Test after issuing a 'logout' to delete the cookies - test_client.get('/delete_tokens') - response = test_client.get('/protected') + +@pytest.mark.parametrize("options", [ + ('/refresh_token', '/post_refresh_protected'), + ('/access_token', '/post_protected') +]) +def test_csrf_disabled(app, options): + app.config['JWT_COOKIE_CSRF_PROTECT'] = False + test_client = app.test_client() + auth_url, post_url = options + + # Get the jwt cookies and csrf double submit tokens + test_client.get(auth_url) + response = test_client.post(post_url) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("options", [ + ('/refresh_token', 'csrf_refresh_token', '/post_refresh_protected'), + ('/access_token', 'csrf_access_token', '/post_protected') +]) +def test_csrf_with_custom_header_names(app, options): + app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'FOO' + app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'FOO' + test_client = app.test_client() + auth_url, csrf_cookie_name, post_url = options + + # Get the jwt cookies and csrf double submit tokens + response = test_client.get(auth_url) + csrf_cookie = _get_cookie_from_response(response, csrf_cookie_name) + csrf_token = csrf_cookie[csrf_cookie_name] + + # Test that you can post with the csrf double submit value + csrf_headers = {'FOO': csrf_token} + response = test_client.post(post_url, headers=csrf_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +@pytest.mark.parametrize("options", [ + ('/refresh_token', 'csrf_refresh_token', '/refresh_protected', '/post_refresh_protected'), + ('/access_token', 'csrf_access_token', '/protected', '/post_protected') +]) +def test_custom_csrf_methods(app, options): + app.config['JWT_CSRF_METHODS'] = ['GET'] + test_client = app.test_client() + auth_url, csrf_cookie_name, get_url, post_url = options + + # Get the jwt cookies and csrf double submit tokens + response = test_client.get(auth_url) + csrf_cookie = _get_cookie_from_response(response, csrf_cookie_name) + csrf_token = csrf_cookie[csrf_cookie_name] + + # Insure we can now do posts without csrf + response = test_client.post(post_url) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + # Insure GET requests now fail without csrf + response = test_client.get(get_url) json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 401 - assert json_data == {'msg': 'Missing cookie "access_token_cookie"'} + assert json_data == {'msg': 'Missing CSRF token in headers'} + + # Insure GET requests now succeed with csrf + csrf_headers = {'X-CSRF-TOKEN': csrf_token} + response = test_client.get(get_url, headers=csrf_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} def test_setting_cookies_wihout_cookies_enabled(app): @@ -278,6 +369,5 @@ def test_cookies_without_csrf(app): refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') assert 'refresh_token_cookie' in refresh_cookie -# TODO test csrf with multi methods # TODO test headers # TODO test cookies and headers together From fcc94dec7b0c4259d31589b7f37c6c71f4b7a816 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 10:58:13 -0600 Subject: [PATCH 13/18] Test various custom response callback loaders --- tests/test_cookies.py | 17 +++++++ tests/test_decode_tokens.py | 22 +++------ tests/test_view_decorators.py | 93 ++++++++++++++++++++++++++++++++++- tests/utils.py | 15 ++++++ 4 files changed, 130 insertions(+), 17 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index f438e9c2..bceb3242 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -124,6 +124,23 @@ def test_default_access_csrf_protection(app, options): assert json_data == {'foo': 'bar'} +@pytest.mark.parametrize("options", [ + ('/refresh_token', '/post_refresh_protected'), + ('/access_token', '/post_protected') +]) +def test_non_matching_csrf_token(app, options): + test_client = app.test_client() + auth_url, post_url = options + + # Get the jwt cookies and csrf double submit tokens + test_client.get(auth_url) + csrf_headers = {'X-CSRF-TOKEN': 'totally_wrong_token'} + response = test_client.post(post_url, headers=csrf_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'CSRF double submit tokens do not match'} + + @pytest.mark.parametrize("options", [ ('/refresh_token', '/post_refresh_protected'), ('/access_token', '/post_protected') diff --git a/tests/test_decode_tokens.py b/tests/test_decode_tokens.py index 08ce6046..9236f334 100644 --- a/tests/test_decode_tokens.py +++ b/tests/test_decode_tokens.py @@ -11,7 +11,7 @@ ) from flask_jwt_extended.config import config from flask_jwt_extended.exceptions import JWTDecodeError -from tests.utils import get_jwt_manager +from tests.utils import get_jwt_manager, encode_token @pytest.fixture(scope='function') @@ -36,16 +36,6 @@ def default_access_token(app): } -def _encode_token(app, token_data): - with app.test_request_context(): - token = jwt.encode( - token_data, - config.decode_key, - algorithm=config.algorithm - ) - return token.decode('utf-8') - - @pytest.mark.parametrize("user_loader_return", [{}, None]) def test_no_user_claims(app, user_loader_return): jwtM = get_jwt_manager(app) @@ -67,7 +57,7 @@ def empty_user_loader_return(identity): @pytest.mark.parametrize("missing_claim", ['jti', 'type', 'identity', 'fresh', 'csrf']) def test_missing_jti_claim(app, default_access_token, missing_claim): del default_access_token[missing_claim] - missing_jwt_token = _encode_token(app, default_access_token) + missing_jwt_token = encode_token(app, default_access_token) with pytest.raises(JWTDecodeError): with app.test_request_context(): @@ -76,7 +66,7 @@ def test_missing_jti_claim(app, default_access_token, missing_claim): def test_bad_token_type(app, default_access_token): default_access_token['type'] = 'banana' - bad_type_token = _encode_token(app, default_access_token) + bad_type_token = encode_token(app, default_access_token) with pytest.raises(JWTDecodeError): with app.test_request_context(): @@ -98,7 +88,7 @@ def test_alternate_identity_claim(app, default_access_token): app.config['JWT_IDENTITY_CLAIM'] = 'sub' # Insure decoding fails if the claim isn't there - token = _encode_token(app, default_access_token) + token = encode_token(app, default_access_token) with pytest.raises(JWTDecodeError): with app.test_request_context(): decode_token(token) @@ -106,7 +96,7 @@ def test_alternate_identity_claim(app, default_access_token): # Insure the claim exists in the decoded jwt del default_access_token['identity'] default_access_token['sub'] = 'username' - token = _encode_token(app, default_access_token) + token = encode_token(app, default_access_token) with app.test_request_context(): decoded = decode_token(token) assert 'sub' in decoded @@ -114,7 +104,7 @@ def test_alternate_identity_claim(app, default_access_token): def test_get_jti(app, default_access_token): - token = _encode_token(app, default_access_token) + token = encode_token(app, default_access_token) with app.test_request_context(): assert default_access_token['jti'] == get_jti(token) diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index cbfb033a..d9265a9c 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -6,7 +6,7 @@ jwt_required, fresh_jwt_required, JWTManager, jwt_refresh_token_required, jwt_optional, create_access_token, create_refresh_token, get_jwt_identity ) -from tests.utils import make_headers +from tests.utils import make_headers, encode_token, get_jwt_manager @pytest.fixture(scope='function') @@ -71,6 +71,7 @@ def test_jwt_required(app): def test_fresh_jwt_required(app): + jwtM = get_jwt_manager(app) url = '/fresh_protected' test_client = app.test_client() @@ -99,6 +100,16 @@ def test_fresh_jwt_required(app): assert response.status_code == 422 assert json_data == {'msg': 'Only access tokens can access this endpoint'} + # Test with custom response + @jwtM.needs_fresh_token_loader + def custom_response(): + return jsonify(msg='foobar'), 201 + + response = test_client.get(url, headers=make_headers(access_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'msg': 'foobar'} + def test_refresh_jwt_required(app): url = '/refresh_protected' @@ -165,6 +176,86 @@ def test_jwt_optional(app): assert response.status_code == 401 assert json_data == {'msg': 'Token has expired'} + +def test_invalid_jwt(app): + url = '/protected' + jwtM = get_jwt_manager(app) + test_client = app.test_client() + invalid_token = "aaaaa.bbbbb.ccccc" + + # Test default response + response = test_client.get(url, headers=make_headers(invalid_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 422 + assert json_data == {'msg': 'Invalid header padding'} + + # Test custom response + @jwtM.invalid_token_loader + def custom_response(err_str): + return jsonify(msg='foobar'), 201 + + response = test_client.get(url, headers=make_headers(invalid_token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'msg': 'foobar'} + + +def test_jwt_missing_claims(app): + url = '/protected' + test_client = app.test_client() + token = encode_token(app, {'foo': 'bar'}) + + response = test_client.get(url, headers=make_headers(token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 422 + assert json_data == {'msg': 'Missing claim: jti'} + + +def test_expired_token(app): + url = '/protected' + jwtM = get_jwt_manager(app) + test_client = app.test_client() + with app.test_request_context(): + token = create_access_token('username', expires_delta=timedelta(minutes=-1)) + + # Test default response + response = test_client.get(url, headers=make_headers(token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Token has expired'} + + # Test custom response + @jwtM.expired_token_loader + def custom_response(): + return jsonify(msg='foobar'), 201 + + response = test_client.get(url, headers=make_headers(token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'msg': 'foobar'} + + +def test_no_token(app): + url = '/protected' + jwtM = get_jwt_manager(app) + test_client = app.test_client() + + # Test default response + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing Authorization Header'} + + # Test custom response + @jwtM.unauthorized_loader + def custom_response(err_str): + return jsonify(msg='foobar'), 201 + + response = test_client.get(url, headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'msg': 'foobar'} + # TODO test different header name and type # TODO test asymmetric crypto # TODO test different name for user claims key in dict diff --git a/tests/utils.py b/tests/utils.py index 9c390a2c..c0ba1b32 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,18 @@ +import jwt + +from flask_jwt_extended.config import config + + +def encode_token(app, token_data): + with app.test_request_context(): + token = jwt.encode( + token_data, + config.decode_key, + algorithm=config.algorithm + ) + return token.decode('utf-8') + + def get_jwt_manager(app): return app.extensions['flask-jwt-extended'] From 81605afabcdfc3ef54b4d8a634052dc1e34889f6 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 11:29:43 -0600 Subject: [PATCH 14/18] Header options specific checks --- tests/test_asymmetric_crypto.py | 0 tests/test_cookies.py | 3 - tests/test_headers.py | 92 +++++++++++++++++++++++++++++++ tests/test_headers_and_cookies.py | 0 tests/test_view_decorators.py | 4 -- 5 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 tests/test_asymmetric_crypto.py create mode 100644 tests/test_headers.py create mode 100644 tests/test_headers_and_cookies.py diff --git a/tests/test_asymmetric_crypto.py b/tests/test_asymmetric_crypto.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_cookies.py b/tests/test_cookies.py index bceb3242..47d22e49 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -385,6 +385,3 @@ def test_cookies_without_csrf(app): assert len(cookies) == 1 refresh_cookie = _get_cookie_from_response(response, 'refresh_token_cookie') assert 'refresh_token_cookie' in refresh_cookie - -# TODO test headers -# TODO test cookies and headers together diff --git a/tests/test_headers.py b/tests/test_headers.py new file mode 100644 index 00000000..849e3586 --- /dev/null +++ b/tests/test_headers.py @@ -0,0 +1,92 @@ +import pytest +from flask import Flask, jsonify, json + +from flask_jwt_extended import JWTManager, jwt_required, create_access_token +from tests.utils import get_jwt_manager + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + JWTManager(app) + + @app.route('/protected', methods=['GET']) + @jwt_required + def access_protected(): + return jsonify(foo='bar') + + return app + + +def test_custom_header_name(app): + app.config['JWT_HEADER_NAME'] = 'Foo' + test_client = app.test_client() + + with app.test_request_context(): + access_token = create_access_token('username') + + # Insure 'default' headers no longer work + access_headers = {'Authorization': 'Bearer {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing Foo Header'} + + # Insure new headers do work + access_headers = {'Foo': 'Bearer {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +def test_custom_header_type(app): + app.config['JWT_HEADER_TYPE'] = 'JWT' + test_client = app.test_client() + + with app.test_request_context(): + access_token = create_access_token('username') + + # Insure 'default' headers no longer work + access_headers = {'Authorization': 'Bearer {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 422 + assert json_data == {'msg': "Bad Authorization header. Expected value 'JWT '"} + + # Insure new headers do work + access_headers = {'Authorization': 'JWT {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + # Insure new headers without a type also work + app.config['JWT_HEADER_TYPE'] = '' + access_headers = {'Authorization': access_token} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +def test_missing_headers(app): + test_client = app.test_client() + jwtM = get_jwt_manager(app) + + # Insure 'default' no headers response + response = test_client.get('/protected', headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': "Missing Authorization Header"} + + # Test custom no headers response + @jwtM.unauthorized_loader + def custom_response(err_str): + return jsonify(foo='bar'), 201 + + response = test_client.get('/protected', headers=None) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 201 + assert json_data == {'foo': "bar"} diff --git a/tests/test_headers_and_cookies.py b/tests/test_headers_and_cookies.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index d9265a9c..f36a20df 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -255,7 +255,3 @@ def custom_response(err_str): json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 201 assert json_data == {'msg': 'foobar'} - -# TODO test different header name and type -# TODO test asymmetric crypto -# TODO test different name for user claims key in dict From 6a5d6d314332f7fafec7600dbf67195fb0b2c330 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 13:34:50 -0600 Subject: [PATCH 15/18] Test headers and cookies. Up to 100% code coverage with pytest --- tests/test_headers.py | 8 +++++ tests/test_headers_and_cookies.py | 57 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/tests/test_headers.py b/tests/test_headers.py index 849e3586..143f310b 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -70,6 +70,14 @@ def test_custom_header_type(app): assert response.status_code == 200 assert json_data == {'foo': 'bar'} + # Insure header with too many parts fails + app.config['JWT_HEADER_TYPE'] = '' + access_headers = {'Authorization': 'Bearer {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert json_data == {'msg': "Bad Authorization header. Expected value ''"} + assert response.status_code == 422 + def test_missing_headers(app): test_client = app.test_client() diff --git a/tests/test_headers_and_cookies.py b/tests/test_headers_and_cookies.py index e69de29b..9af43d32 100644 --- a/tests/test_headers_and_cookies.py +++ b/tests/test_headers_and_cookies.py @@ -0,0 +1,57 @@ +import pytest +from flask import Flask, jsonify, json + +from flask_jwt_extended import ( + JWTManager, jwt_required, create_access_token, set_access_cookies +) + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies'] + JWTManager(app) + + @app.route('/cookie_login', methods=['GET']) + def cookie_login(): + resp = jsonify(login=True) + access_token = create_access_token('username') + set_access_cookies(resp, access_token) + return resp + + @app.route('/protected', methods=['GET']) + @jwt_required + def access_protected(): + return jsonify(foo='bar') + + return app + + +def test_header_access(app): + test_client = app.test_client() + with app.test_request_context(): + access_token = create_access_token('username') + + access_headers = {'Authorization': 'Bearer {}'.format(access_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +def test_cookie_access(app): + test_client = app.test_client() + test_client.get('/cookie_login') + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} + + +def test_no_jwt_in_request(app): + test_client = app.test_client() + response = test_client.get('/protected') + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 401 + assert json_data == {'msg': 'Missing JWT in headers and cookies'} From 0abd30d7b742345cbb5b600210d7eeb79c4ac9ff Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 13:48:32 -0600 Subject: [PATCH 16/18] Test asymmetric support --- tests/test_asymmetric_crypto.py | 69 +++++++++++++++++++++++++++++++++ tests/test_view_decorators.py | 14 +++++++ 2 files changed, 83 insertions(+) diff --git a/tests/test_asymmetric_crypto.py b/tests/test_asymmetric_crypto.py index e69de29b..73850cd7 100644 --- a/tests/test_asymmetric_crypto.py +++ b/tests/test_asymmetric_crypto.py @@ -0,0 +1,69 @@ +import pytest +from flask import Flask, jsonify, json + +from flask_jwt_extended import JWTManager, jwt_required, create_access_token + +RSA_PRIVATE = """ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP +DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo +xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB +AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 +NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D +G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H +bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ +Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ +tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 +cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA +mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv +lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d +E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== +-----END RSA PRIVATE KEY----- +""" + +RSA_PUBLIC = """ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd +E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 +mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= +-----END RSA PUBLIC KEY----- +""" + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['JWT_SECRET_KEY'] = 'foobarbaz' + app.config['JWT_PUBLIC_KEY'] = RSA_PUBLIC + app.config['JWT_PRIVATE_KEY'] = RSA_PRIVATE + JWTManager(app) + + @app.route('/protected', methods=['GET']) + @jwt_required + def protected(): + return jsonify(foo='bar') + + return app + + +def test_asymmetric_cropto(app): + test_client = app.test_client() + + with app.test_request_context(): + hs256_token = create_access_token('username') + app.config['JWT_ALGORITHM'] = 'RS256' + rs256_token = create_access_token('username') + + # Insure the symmetric token does not work now + access_headers = {'Authorization': 'Bearer {}'.format(hs256_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 422 + assert json_data == {'msg': 'The specified alg value is not allowed'} + + # Insure the asymmetric token does work + access_headers = {'Authorization': 'Bearer {}'.format(rs256_token)} + response = test_client.get('/protected', headers=access_headers) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_data == {'foo': 'bar'} diff --git a/tests/test_view_decorators.py b/tests/test_view_decorators.py index f36a20df..8292bd3e 100644 --- a/tests/test_view_decorators.py +++ b/tests/test_view_decorators.py @@ -255,3 +255,17 @@ def custom_response(err_str): json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 201 assert json_data == {'msg': 'foobar'} + + +def test_different_token_algorightm(app): + url = '/protected' + test_client = app.test_client() + with app.test_request_context(): + token = create_access_token('username') + + app.config['JWT_ALGORITHM'] = 'HS512' + + response = test_client.get(url, headers=make_headers(token)) + json_data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 422 + assert json_data == {'msg': 'The specified alg value is not allowed'} From f60c94d6d48465a94c39be88f61fc4a14cf9ef6e Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 13:49:14 -0600 Subject: [PATCH 17/18] Remove old tests --- tests/old.py | 590 ---------- tests_old/app.py | 464 -------- tests_old/test_blacklist.py | 168 --- tests_old/test_config.py | 229 ---- tests_old/test_jwt_encode_decode.py | 409 ------- tests_old/test_jwt_manager.py | 237 ---- tests_old/test_protected_endpoints.py | 1138 -------------------- tests_old/test_user_claims_verification.py | 87 -- tests_old/test_user_loader.py | 135 --- 9 files changed, 3457 deletions(-) delete mode 100644 tests/old.py delete mode 100644 tests_old/app.py delete mode 100644 tests_old/test_blacklist.py delete mode 100644 tests_old/test_config.py delete mode 100644 tests_old/test_jwt_encode_decode.py delete mode 100644 tests_old/test_jwt_manager.py delete mode 100644 tests_old/test_protected_endpoints.py delete mode 100644 tests_old/test_user_claims_verification.py delete mode 100644 tests_old/test_user_loader.py diff --git a/tests/old.py b/tests/old.py deleted file mode 100644 index 55bb5f5b..00000000 --- a/tests/old.py +++ /dev/null @@ -1,590 +0,0 @@ -import pytest -from datetime import timedelta -from flask import Flask, jsonify, json -from werkzeug.http import parse_cookie - -from flask_jwt_extended import ( - JWTManager, create_access_token, create_refresh_token, jwt_required, - jwt_refresh_token_required, fresh_jwt_required, jwt_optional, - get_current_user, set_access_cookies, - set_refresh_cookies -) - -RSA_PRIVATE = """ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP -DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo -xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB -AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 -NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D -G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H -bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ -Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ -tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 -cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA -mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv -lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d -E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== ------END RSA PRIVATE KEY----- -""" - -RSA_PUBLIC = """ ------BEGIN RSA PUBLIC KEY----- -MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd -E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 -mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= ------END RSA PUBLIC KEY----- -""" - - -def cartesian_product_general_configs(): - jwt_identity_claims = ['identity', 'sub'] - - configs = [] - for identity in jwt_identity_claims: - configs.append({ - 'JWT_SECRET_KEY': 'testing_secret_key', - 'JWT_ALGORITHM': 'HS256', - 'JWT_IDENTITY_CLAIM': identity - }) - configs.append({ - 'JWT_PUBLIC_KEY': RSA_PUBLIC, - 'JWT_PRIVATE_KEY': RSA_PRIVATE, - 'JWT_ALGORITHM': 'RS256', - 'JWT_IDENTITY_CLAIM': identity - }) - return configs - - -def cartesian_product_header_configs(): - token_locations = ['headers', ['cookies', 'headers']] - header_names = ['Authorization', 'Foo'] - header_types = ['Bearer', 'JWT', ''] - - configs = [] - for location in token_locations: - for header_name in header_names: - for header_type in header_types: - config_combination = { - 'JWT_TOKEN_LOCATION': location, - 'JWT_HEADER_NAME': header_name, - 'JWT_HEADER_TYPE': header_type - } - configs.append(config_combination) - return configs - - -def cartesian_product_cookie_configs(): - token_locations = [['cookies'], ['cookies', 'headers']] - access_cookie_names = ['access_token_cookie', 'access_foo'] - refresh_cookie_names = ['refresh_token_cookie', 'refresh_foo'] - - configs = [] - for location in token_locations: - for access_name in access_cookie_names: - for refresh_name in refresh_cookie_names: - config_combination = { - 'JWT_TOKEN_LOCATION': location, - 'JWT_ACCESS_COOKIE_NAME': access_name, - 'JWT_REFRESH_COOKIE_NAME': refresh_name - } - configs.append(config_combination) - return configs - - -COOKIE_COMBINATIONS = cartesian_product_cookie_configs() -HEADER_COMBINATIONS = cartesian_product_header_configs() -CONFIG_COMBINATIONS = cartesian_product_general_configs() - - -@pytest.fixture(scope='function', params=CONFIG_COMBINATIONS) -def app(request): - app = Flask(__name__) - - for key, value in request.param.items(): - app.config[key] = value - - JWTManager(app) - - @app.route('/fresh_access_jwt', methods=['POST']) - def fresh_access_jwt(): - access_token = create_access_token('username', fresh=True) - return jsonify(jwt=access_token) - - @app.route('/cookie_fresh_access_jwt', methods=['POST']) - def cookie_fresh_access_jwt(): - access_token = create_access_token('username', fresh=True) - resp = jsonify(success=True) - set_access_cookies(resp, access_token) - return resp - - @app.route('/not_fresh_access_jwt', methods=['POST']) - def not_fresh_access_jwt(): - access_token = create_access_token('username', fresh=False) - return jsonify(jwt=access_token) - - @app.route('/cookie_not_fresh_access_jwt', methods=['POST']) - def cookie_not_fresh_access_jwt(): - access_token = create_access_token('username', fresh=False) - resp = jsonify(success=True) - set_access_cookies(resp, access_token) - return resp - - @app.route('/custom_expires_access_jwt', methods=['POST']) - def custom_expires_access(): - expires = timedelta(minutes=5) - access_token = create_access_token('username', expires_delta=expires) - return jsonify(jwt=access_token) - - @app.route('/refresh_jwt', methods=['POST']) - def refresh_jwt(): - refresh_token = create_refresh_token('username') - return jsonify(jwt=refresh_token) - - @app.route('/cookie_refresh_jwt', methods=['POST']) - def cookie_refresh_jwt(): - refresh_token = create_refresh_token('username') - resp = jsonify(success=True) - set_refresh_cookies(resp, refresh_token) - return resp - - @app.route('/custom_expires_refresh_jwt', methods=['POST']) - def custom_expires_refresh_jwt(): - expires = timedelta(minutes=5) - refresh_token = create_refresh_token('username', expires_delta=expires) - return jsonify(jwt=refresh_token) - - @app.route('/protected', methods=['GET', 'POST']) - @jwt_required - def protected(): - return jsonify(foo='bar') - - @app.route('/fresh_protected', methods=['GET', 'POST']) - @fresh_jwt_required - def fresh_protected(): - return jsonify(foo='bar') - - @app.route('/refresh_protected', methods=['GET', 'POST']) - @jwt_refresh_token_required - def refresh_protected(): - return jsonify(foo='bar') - - @app.route('/optional_protected', methods=['GET', 'POST']) - @jwt_optional - def optional_protected(): - if get_current_user(): - return jsonify(foo='baz') - else: - return jsonify(foo='bar') - - @app.route('/not_protected', methods=['GET', 'POST']) - def not_protected(): - return jsonify(foo='bar') - - return app - - -@pytest.fixture(scope='function', params=HEADER_COMBINATIONS) -def headers_app(request, app): - for key, value in request.param.items(): - app.config[key] = value - return app - - -@pytest.fixture(scope='function', params=COOKIE_COMBINATIONS) -def cookies_app(request, app): - for key, value in request.param.items(): - app.config[key] = value - return app - - -def get_fresh_jwt(test_client): - response = test_client.post('/fresh_access_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def get_non_fresh_jwt(test_client): - response = test_client.post('/not_fresh_access_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def get_refresh_jwt(test_client): - response = test_client.post('/refresh_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def _get_jwt_from_response_cookie(response, cookie_name): - cookies = response.headers.getlist('Set-Cookie') - for cookie in cookies: - parsed_cookie = parse_cookie(cookie) - for c_key, c_val in parsed_cookie.items(): - if c_key == cookie_name: - return c_val - raise Exception('jwt cooke value not found') - - -def get_cookie_fresh_jwt(test_client): - response = test_client.post('/cookie_fresh_access_jwt') - assert response.status_code == 200 - - app = test_client.application - access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - return _get_jwt_from_response_cookie(response, access_cookie_name) - - -def get_cookie_non_fresh_jwt(test_client): - response = test_client.post('/cookie_not_fresh_access_jwt') - assert response.status_code == 200 - - app = test_client.application - access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - return _get_jwt_from_response_cookie(response, access_cookie_name) - - -def get_cookie_refresh_jwt(test_client): - response = test_client.post('/cookie_refresh_jwt') - assert response.status_code == 200 - - app = test_client.application - access_cookie_name = app.config['JWT_REFRESH_COOKIE_NAME'] - return _get_jwt_from_response_cookie(response, access_cookie_name) - - -def make_request(test_client, request_type, request_url, headers=None, cookies=None): - if cookies is None: - cookies = {} - if cookies: - for c_key, c_val in cookies.items(): - test_client.set_cookie('/', c_key, c_val) - - request_type = getattr(test_client, request_type.lower()) - return request_type( - request_url, - content_type='application/json', - headers=headers - ) - - -def make_jwt_headers_request(test_client, jwt, request_type, request_url): - app = test_client.application - header_name = app.config['JWT_HEADER_NAME'] - header_type = app.config['JWT_HEADER_TYPE'] - headers = {header_name: '{} {}'.format(header_type, jwt).strip()} - return make_request(test_client, request_type, request_url, headers=headers) - - -def make_jwt_cookies_request(test_client, jwt, request_type, request_url): - app = test_client.application - cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - cookies = {cookie_name: jwt} - return make_request(test_client, request_type, request_url, cookies=cookies) - - -@pytest.mark.parametrize("fail_endpoint", [ - '/protected', - '/fresh_protected', - '/refresh_protected', -]) -@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) -def test_blocked_endpoints_without_jwt(app, fail_endpoint, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - test_client = app.test_client() - response = make_request(test_client, 'GET', fail_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - expected_errors = ( - {'msg': 'Missing Authorization Header'}, - {'msg': 'Missing cookie "access_token_cookie"'}, - {'msg': 'Missing cookie "refresh_token_cookie"'}, - {'msg': 'Missing JWT in headers and cookies'}, - ) - assert json_data in expected_errors - assert response.status_code == 401 - - -@pytest.mark.parametrize("success_endpoint", [ - '/optional_protected', - '/not_protected', -]) -@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) -def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - test_client = app.test_client() - response = make_request(test_client, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/fresh_protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - fresh_jwt = get_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) -def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - fresh_jwt = get_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'msg': 'Only refresh tokens can access this endpoint'} - assert response.status_code == 422 - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - jwt = get_non_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("failure_endpoint", [ - '/refresh_protected', - '/fresh_protected' -]) -def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - jwt = get_non_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - expected_errors = ( - (422, {'msg': 'Only refresh tokens can access this endpoint'}), - (401, {'msg': 'Fresh token required'}) - ) - assert (response.status_code, json_data) in expected_errors - - -@pytest.mark.parametrize("success_endpoint", [ - '/refresh_protected', - '/not_protected' -]) -def test_accessable_endpoints_with_refresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - jwt = get_refresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("failure_endpoint", [ - '/fresh_protected', - '/protected', - '/optional_protected' -]) -def test_blocked_endpoints_with_refresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - jwt = get_refresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'msg': 'Only access tokens can access this endpoint'} - assert response.status_code == 422 - - -@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) -def test_bad_header_name_blocks_protected_endpoints(app, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - app.config['JWT_HEADER_NAME'] = 'Foo' - - test_client = app.test_client() - jwt = get_fresh_jwt(test_client) - - headers = {'Authorization': 'Bearer {}'.format(jwt)} - response = make_request(test_client, 'GET', '/protected', headers=headers) - json_data = json.loads(response.get_data(as_text=True)) - - expected_json = ( - {'msg': 'Missing Foo Header'}, - {'msg': 'Missing JWT in headers and cookies'} - ) - assert json_data in expected_json - assert response.status_code == 401 - - -@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) -@pytest.mark.parametrize("header_type", ['Foo', '']) -def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_type): - app.config['JWT_TOKEN_LOCATION'] = token_location - app.config['JWT_HEADER_TYPE'] = header_type - - test_client = app.test_client() - jwt = get_fresh_jwt(test_client) - - headers = {'Authorization': 'Bearer {}'.format(jwt)} - response = make_request(test_client, 'GET', '/protected', headers=headers) - json_data = json.loads(response.get_data(as_text=True)) - - expected_json = ( - {'msg': "Bad Authorization header. Expected value ''"}, - {'msg': "Bad Authorization header. Expected value 'Foo '"} - ) - - assert json_data in expected_json - assert response.status_code == 422 - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/fresh_protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_fresh_jwt_in_cookies(cookies_app, success_endpoint): - test_client = cookies_app.test_client() - fresh_jwt = get_cookie_fresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -# TODO when using cookies, actually send the wrong cookie type (refresh/access) -# into the header that expects the other type. The cookies have different -# names, so this test doesn't actually test that case -@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) -def test_blocked_endpoints_with_fresh_jwt_in_headers(cookies_app, failure_endpoint): - test_client = cookies_app.test_client() - fresh_jwt = get_cookie_fresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - expected_errors = ( - {'msg': 'Missing cookie "{}"'.format(cookies_app.config['JWT_REFRESH_COOKIE_NAME'])}, - {'msg': 'Missing JWT in headers and cookies'} - ) - assert json_data in expected_errors - assert response.status_code == 401 - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_non_fresh_jwt_in_cookies(cookies_app, success_endpoint): - test_client = cookies_app.test_client() - fresh_jwt = get_cookie_non_fresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("failure_endpoint", [ - '/refresh_protected', - '/fresh_protected' -]) -def test_blocked_endpoints_with_non_fresh_jwt_in_cookies(cookies_app, failure_endpoint): - test_client = cookies_app.test_client() - fresh_jwt = get_cookie_non_fresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - refresh_cookie_name = cookies_app.config['JWT_REFRESH_COOKIE_NAME'] - expected_errors = ( - {'msg': 'Missing cookie "{}"'.format(refresh_cookie_name)}, - {'msg': 'Missing JWT in headers and cookies'}, - {'msg': 'Fresh token required'} - ) - assert json_data in expected_errors - assert response.status_code == 401 - - -@pytest.mark.parametrize("success_endpoint", [ - '/refresh_protected', - '/not_protected' -]) -def test_accessable_endpoints_with_refresh_jwt_in_cookies(cookies_app, success_endpoint): - test_client = cookies_app.test_client() - refresh_jwt = get_cookie_refresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, refresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert json_data == {'foo': 'bar'} - assert response.status_code == 200 - - -@pytest.mark.parametrize("failure_endpoint", [ - '/fresh_protected', - '/protected', - '/optional_protected' -]) -def test_blocked_endpoints_with_refresh_jwt_in_cookies(cookies_app, failure_endpoint): - test_client = cookies_app.test_client() - refresh_jwt = get_cookie_refresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, refresh_jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - # TODO is this right? I would expect an error about missing the cookie. I - # think this is broke as we are only sending in the access cookie - # not the refresh cookie when doing make_jwt_cookies_request - expected_errors = ( - {'msg': 'Only access tokens can access this endpoint'}, - ) - assert json_data in expected_errors - assert response.status_code == 422 - - -# TODO test sending in headers when cookie_locations and vice versa -# TODO when using cookies with csrf, test GET and POST requests -# TODO test that verifies the jwt identity claim actually changes (sub/identity) -# TODO test possible combinations for jwt_optional -# TODO simple test that the other cookie overrides are working -# TODO test having the access and refresh cookie be the same name? - - -# Various options we want to test stuff here (with different expectations for -# success or failure) -# - JWT in cookies and pass in with cookies -# - JWT in cookies and pass in with headers -# - JWT in headers and pass in with cookies -# - JWT in headers and pass in with headers -# - JWT in headers and cookies and pass in with cookies -# - JWT in headers and cookies and pass in with headers -# -# Everything we want to actually test with the above configurations: -# - all protected endpoints with expected jwts -# - all protected endpoints with unexpected jwts -# - all protected endpoints with expired jwts -# - all protected endpoints with tampered with jwts -# - all protected endpoints with tampered with no jwts -# - all protected endpoints with tampered with no revoked jts -# - all protected endpoints with tampered with no user loader from jwts -# - all protected endpoints with tampered with no no user loader from jwts -# - all protected endpoints with tampered with verified claims in jwts -# - all protected endpoints with tampered with failed verified claims in jwts diff --git a/tests_old/app.py b/tests_old/app.py deleted file mode 100644 index c736b74f..00000000 --- a/tests_old/app.py +++ /dev/null @@ -1,464 +0,0 @@ -import pytest -from datetime import timedelta -from flask import Flask, jsonify, json -from werkzeug.http import parse_cookie - -from flask_jwt_extended import ( - JWTManager, create_access_token, create_refresh_token, jwt_required, - jwt_refresh_token_required, fresh_jwt_required, jwt_optional, - get_current_user, set_access_cookies -) - -RSA_PRIVATE = """ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP -DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo -xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB -AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 -NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D -G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H -bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ -Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ -tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 -cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA -mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv -lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d -E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== ------END RSA PRIVATE KEY----- -""" - -RSA_PUBLIC = """ ------BEGIN RSA PUBLIC KEY----- -MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd -E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 -mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= ------END RSA PUBLIC KEY----- -""" - - -def cartesian_product_general_configs(): - jwt_identity_claims = ['identity', 'sub'] - - configs = [] - for identity in jwt_identity_claims: - configs.append({ - 'JWT_SECRET_KEY': 'testing_secret_key', - 'JWT_ALGORITHM': 'HS256', - 'JWT_IDENTITY_CLAIM': identity - }) - configs.append({ - 'JWT_PUBLIC_KEY': RSA_PUBLIC, - 'JWT_PRIVATE_KEY': RSA_PRIVATE, - 'JWT_ALGORITHM': 'RS256', - 'JWT_IDENTITY_CLAIM': identity - }) - return configs - - -def cartesian_product_header_configs(): - token_locations = ['headers', ['cookies', 'headers']] - header_names = ['Authorization', 'Foo'] - header_types = ['Bearer', 'JWT', ''] - - configs = [] - for location in token_locations: - for header_name in header_names: - for header_type in header_types: - config_combination = { - 'JWT_TOKEN_LOCATION': location, - 'JWT_HEADER_NAME': header_name, - 'JWT_HEADER_TYPE': header_type - } - configs.append(config_combination) - return configs - - -def cartesian_product_cookie_configs(): - token_locations = [['cookies'], ['cookies', 'headers']] - access_cookie_names = ['access_token_cookie', 'access_foo'] - refresh_cookie_names = ['refresh_token_cookie', 'refresh_foo'] - - configs = [] - for location in token_locations: - for access_name in access_cookie_names: - for refresh_name in refresh_cookie_names: - config_combination = { - 'JWT_TOKEN_LOCATION': location, - 'JWT_ACCESS_COOKIE_NAME': access_name, - 'JWT_REFRESH_COOKIE_NAME': refresh_name - } - configs.append(config_combination) - return configs - - -COOKIE_COMBINATIONS = cartesian_product_cookie_configs() -HEADER_COMBINATIONS = cartesian_product_header_configs() -CONFIG_COMBINATIONS = cartesian_product_general_configs() - - -@pytest.fixture(scope='function', params=CONFIG_COMBINATIONS) -def app(request): - app = Flask(__name__) - - for key, value in request.param.items(): - app.config[key] = value - - JWTManager(app) - - @app.route('/fresh_access_jwt', methods=['POST']) - def fresh_access_jwt(): - access_token = create_access_token('username', fresh=True) - return jsonify(jwt=access_token) - - @app.route('/cookie_fresh_access_jwt', methods=['POST']) - def cookie_fresh_access_jwt(): - access_token = create_access_token('username', fresh=True) - resp = jsonify(success=True) - set_access_cookies(resp, access_token) - return resp - - @app.route('/not_fresh_access_jwt', methods=['POST']) - def not_fresh_access_jwt(): - access_token = create_access_token('username', fresh=False) - return jsonify(jwt=access_token) - - @app.route('/custom_expires_access_jwt', methods=['POST']) - def custom_expires_access(): - expires = timedelta(minutes=5) - access_token = create_access_token('username', expires_delta=expires) - return jsonify(jwt=access_token) - - @app.route('/refresh_jwt', methods=['POST']) - def refresh_jwt(): - refresh_token = create_refresh_token('username') - return jsonify(jwt=refresh_token) - - @app.route('/custom_expires_refresh_jwt', methods=['POST']) - def custom_expires_refresh_jwt(): - expires = timedelta(minutes=5) - refresh_token = create_refresh_token('username', expires_delta=expires) - return jsonify(jwt=refresh_token) - - @app.route('/protected', methods=['GET', 'POST']) - @jwt_required - def protected(): - return jsonify(foo='bar') - - @app.route('/fresh_protected', methods=['GET', 'POST']) - @fresh_jwt_required - def fresh_protected(): - return jsonify(foo='bar') - - @app.route('/refresh_protected', methods=['GET', 'POST']) - @jwt_refresh_token_required - def refresh_protected(): - return jsonify(foo='bar') - - @app.route('/optional_protected', methods=['GET', 'POST']) - @jwt_optional - def optional_protected(): - if get_current_user(): - return jsonify(foo='baz') - else: - return jsonify(foo='bar') - - @app.route('/not_protected', methods=['GET', 'POST']) - def not_protected(): - return jsonify(foo='bar') - - return app - - -@pytest.fixture(scope='function', params=HEADER_COMBINATIONS) -def headers_app(request, app): - for key, value in request.param.items(): - app.config[key] = value - return app - - -@pytest.fixture(scope='function', params=COOKIE_COMBINATIONS) -def cookies_app(request, app): - for key, value in request.param.items(): - app.config[key] = value - return app - - -def get_fresh_jwt(test_client): - response = test_client.post('/fresh_access_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def get_non_fresh_jwt(test_client): - response = test_client.post('/not_fresh_access_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def get_refresh_jwt(test_client): - response = test_client.post('/refresh_jwt') - json_data = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert 'jwt' in json_data - return json_data['jwt'] - - -def get_cookie_fresh_jwt(test_client): - app = test_client.application - access_cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - - response = test_client.post('/cookie_fresh_access_jwt') - assert response.status_code == 200 - - cookies = response.headers.getlist('Set-Cookie') - for cookie in cookies: - parsed_cookie = parse_cookie(cookie) - for c_key, c_val in parsed_cookie.items(): - if c_key == access_cookie_name: - return c_val - raise Exception('jwt cooke value not found') - - -def make_request(test_client, request_type, request_url, headers=None, cookies=None): - if cookies is None: - cookies = {} - if cookies: - for c_key, c_val in cookies.items(): - test_client.set_cookie('/', c_key, c_val) - - request_type = getattr(test_client, request_type.lower()) - return request_type( - request_url, - content_type='application/json', - headers=headers - ) - - -def make_jwt_headers_request(test_client, jwt, request_type, request_url): - app = test_client.application - header_name = app.config['JWT_HEADER_NAME'] - header_type = app.config['JWT_HEADER_TYPE'] - headers = {header_name: '{} {}'.format(header_type, jwt).strip()} - return make_request(test_client, request_type, request_url, headers=headers) - - -def make_jwt_cookies_request(test_client, jwt, request_type, request_url): - app = test_client.application - cookie_name = app.config['JWT_ACCESS_COOKIE_NAME'] - cookies = {cookie_name: jwt} - return make_request(test_client, request_type, request_url, cookies=cookies) - - -@pytest.mark.parametrize("fail_endpoint", [ - '/protected', - '/fresh_protected', - '/refresh_protected', -]) -@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) -def test_blocked_endpoints_without_jwt(app, fail_endpoint, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - test_client = app.test_client() - response = make_request(test_client, 'GET', fail_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - expected_errors = ( - {'msg': 'Missing Authorization Header'}, - {'msg': 'Missing cookie "access_token_cookie"'}, - {'msg': 'Missing cookie "refresh_token_cookie"'}, - {'msg': 'Missing JWT in headers and cookies'}, - ) - assert response.status_code == 401 - assert json_data in expected_errors - - -@pytest.mark.parametrize("success_endpoint", [ - '/optional_protected', - '/not_protected', -]) -@pytest.mark.parametrize('token_location', ['headers', 'cookies', ['cookies', 'headers']]) -def test_accessable_endpoints_without_jwt(app, success_endpoint, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - test_client = app.test_client() - response = make_request(test_client, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_data == {'foo': 'bar'} - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/fresh_protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_fresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - fresh_jwt = get_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_data == {'foo': 'bar'} - - -@pytest.mark.parametrize("failure_endpoint", ['/refresh_protected']) -def test_blocked_endpoints_with_fresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - fresh_jwt = get_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, fresh_jwt, 'GET', failure_endpoint) - 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'} - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_non_fresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - jwt = get_non_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_data == {'foo': 'bar'} - - -@pytest.mark.parametrize("failure_endpoint", [ - '/refresh_protected', - '/fresh_protected' -]) -def test_blocked_endpoints_with_non_fresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - jwt = get_non_fresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - expected_errors = ( - (422, {'msg': 'Only refresh tokens can access this endpoint'}), - (401, {'msg': 'Fresh token required'}) - ) - assert (response.status_code, json_data) in expected_errors - - -@pytest.mark.parametrize("success_endpoint", [ - '/refresh_protected', - '/not_protected' -]) -def test_accessable_endpoints_with_refresh_jwt_in_headers(headers_app, success_endpoint): - test_client = headers_app.test_client() - jwt = get_refresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_data == {'foo': 'bar'} - - -@pytest.mark.parametrize("failure_endpoint", [ - '/fresh_protected', - '/protected', - '/optional_protected' -]) -def test_blocked_endpoints_with_refresh_jwt_in_headers(headers_app, failure_endpoint): - test_client = headers_app.test_client() - jwt = get_refresh_jwt(test_client) - response = make_jwt_headers_request(test_client, jwt, 'GET', failure_endpoint) - 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'} - - -@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) -def test_bad_header_name_blocks_protected_endpoints(app, token_location): - app.config['JWT_TOKEN_LOCATION'] = token_location - app.config['JWT_HEADER_NAME'] = 'Foo' - - test_client = app.test_client() - jwt = get_fresh_jwt(test_client) - - headers = {'Authorization': 'Bearer {}'.format(jwt)} - response = make_request(test_client, 'GET', '/protected', headers=headers) - json_data = json.loads(response.get_data(as_text=True)) - - expected_json = ( - {'msg': 'Missing Foo Header'}, - {'msg': 'Missing JWT in headers and cookies'} - ) - assert response.status_code == 401 - assert json_data in expected_json - - -@pytest.mark.parametrize("token_location", ['headers', ['cookies', 'headers']]) -@pytest.mark.parametrize("header_type", ['Foo', '']) -def test_bad_header_type_blocks_protected_endpoints(app, token_location, header_type): - app.config['JWT_TOKEN_LOCATION'] = token_location - app.config['JWT_HEADER_TYPE'] = header_type - - test_client = app.test_client() - jwt = get_fresh_jwt(test_client) - - headers = {'Authorization': 'Bearer {}'.format(jwt)} - response = make_request(test_client, 'GET', '/protected', headers=headers) - json_data = json.loads(response.get_data(as_text=True)) - - expected_json = ( - {'msg': "Bad Authorization header. Expected value ''"}, - {'msg': "Bad Authorization header. Expected value 'Foo '"} - ) - - assert response.status_code == 422 - assert json_data in expected_json - - -@pytest.mark.parametrize("success_endpoint", [ - '/protected', - '/fresh_protected', - '/optional_protected', - '/not_protected', -]) -def test_accessable_endpoints_with_fresh_jwt_in_cookies(cookies_app, success_endpoint): - test_client = cookies_app.test_client() - fresh_jwt = get_cookie_fresh_jwt(test_client) - response = make_jwt_cookies_request(test_client, fresh_jwt, 'GET', success_endpoint) - json_data = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_data == {'foo': 'bar'} - -# TODO test sending in headers when cookie_locations and vice versa -# TODO when using cookies with csrf, test GET and POST requests -# TODO test that verifies the jwt identity claim actually changes (sub/identity) -# TODO test possible combinations for jwt_optional -# TODO simple test that the other cookie overrides are working - - -# Various options we want to test stuff here (with different expectations for -# success or failure) -# - JWT in cookies and pass in with cookies -# - JWT in cookies and pass in with headers -# - JWT in headers and pass in with cookies -# - JWT in headers and pass in with headers -# - JWT in headers and cookies and pass in with cookies -# - JWT in headers and cookies and pass in with headers -# -# Everything we want to actually test with the above configurations: -# - all protected endpoints with expected jwts -# - all protected endpoints with unexpected jwts -# - all protected endpoints with expired jwts -# - all protected endpoints with tampered with jwts -# - all protected endpoints with tampered with no jwts -# - all protected endpoints with tampered with no revoked jts -# - all protected endpoints with tampered with no user loader from jwts -# - all protected endpoints with tampered with no no user loader from jwts -# - all protected endpoints with tampered with verified claims in jwts -# - all protected endpoints with tampered with failed verified claims in jwts diff --git a/tests_old/test_blacklist.py b/tests_old/test_blacklist.py deleted file mode 100644 index 4a6749c1..00000000 --- a/tests_old/test_blacklist.py +++ /dev/null @@ -1,168 +0,0 @@ -import unittest -import json - -from flask import Flask, jsonify, request -from flask_jwt_extended.utils import get_jwt_identity, get_jti - -from flask_jwt_extended import ( - JWTManager, create_access_token, jwt_required, create_refresh_token, - jwt_refresh_token_required, fresh_jwt_required -) - - -class TestEndpoints(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.app.config['JWT_BLACKLIST_ENABLED'] = True - self.app.config['JWT_IDENTITY_CLAIM'] = 'sub' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - self.blacklist = set() - - @self.jwt_manager.token_in_blacklist_loader - def token_in_blacklist(decoded_token): - jti = decoded_token['jti'] - return jti in self.blacklist - - @self.app.route('/auth/login', methods=['POST']) - def login(): - username = request.get_json()['username'] - ret = { - 'access_token': create_access_token(username, fresh=True), - 'refresh_token': create_refresh_token(username) - } - return jsonify(ret), 200 - - @self.app.route('/auth/refresh', methods=['POST']) - @jwt_refresh_token_required - def refresh(): - username = get_jwt_identity() - ret = {'access_token': create_access_token(username, fresh=False)} - return jsonify(ret), 200 - - @self.app.route('/auth/revoke/', methods=['POST']) - def revoke(jti): - self.blacklist.add(jti) - return jsonify({"msg": "Token revoked"}) - - @self.app.route('/auth/unrevoke/', methods=['POST']) - def unrevoke(jti): - self.blacklist.remove(jti) - return jsonify({"msg": "Token unrevoked"}) - - @self.app.route('/protected', methods=['POST']) - @jwt_required - def protected(): - return jsonify({"hello": "world"}) - - @self.app.route('/protected-fresh', methods=['POST']) - @fresh_jwt_required - def protected_fresh(): - return jsonify({"hello": "world"}) - - def _login(self, username): - post_data = {'username': username} - response = self.client.post('/auth/login', data=json.dumps(post_data), - content_type='application/json') - data = json.loads(response.get_data(as_text=True)) - return data['access_token'], data['refresh_token'] - - def _jwt_post(self, url, jwt=None): - if jwt: - header = {'Authorization': 'Bearer {}'.format(jwt)} - response = self.client.post(url, headers=header) - else: - response = self.client.post(url) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_revoke_access_token(self): - # Check access and refresh tokens - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh'] - - # Generate our tokens - access_token, _ = self._login('user') - with self.app.app_context(): - access_jti = get_jti(access_token) - - # Make sure we can access a protected endpoint - status_code, data = self._jwt_post('/protected', access_token) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'hello': 'world'}) - - # Revoke our access token - status, data = self._jwt_post('/auth/revoke/{}'.format(access_jti)) - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'Token revoked'}) - - # Verify the access token can no longer access a protected endpoint - status_code, data = self._jwt_post('/protected', access_token) - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Token has been revoked'}) - - def test_revoke_refresh_token(self): - # Check access and refresh tokens - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh'] - - # Generate our tokens - _, refresh_token = self._login('user') - with self.app.app_context(): - refresh_jti = get_jti(refresh_token) - - # Make sure we can access a protected endpoint - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - - # Revoke our access token - status, data = self._jwt_post('/auth/revoke/{}'.format(refresh_jti)) - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'Token revoked'}) - - # Verify the access token can no longer access a protected endpoint - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Token has been revoked'}) - - def test_revoked_token_with_access_blacklist_only(self): - # Setup to only revoke refresh tokens - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['refresh'] - - # Generate our tokens - access_token, refresh_token = self._login('user') - with self.app.app_context(): - access_jti = get_jti(access_token) - refresh_jti = get_jti(refresh_token) - - # Revoke both tokens (even though app is only configured to look - # at revoked refresh tokens) - self._jwt_post('/auth/revoke/{}'.format(access_jti)) - self._jwt_post('/auth/revoke/{}'.format(refresh_jti)) - - # Make sure we can still access a protected endpoint with the access token - status_code, data = self._jwt_post('/protected', access_token) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'hello': 'world'}) - - # Make sure that the refresh token kicks us back out - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Token has been revoked'}) - - def test_bad_blacklist_settings(self): - # Disable the token in blacklist check function - self.jwt_manager.token_in_blacklist_loader(None) - - access_token, _ = self._login('user') - - # Check that accessing a jwt_required endpoint raises a runtime error - with self.assertRaises(RuntimeError): - self._jwt_post('/protected', access_token) - - # Check calling blacklist function if invalid blacklist check type - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'banana'] - with self.assertRaises(RuntimeError): - self._jwt_post('/protected', access_token) diff --git a/tests_old/test_config.py b/tests_old/test_config.py deleted file mode 100644 index 988bad79..00000000 --- a/tests_old/test_config.py +++ /dev/null @@ -1,229 +0,0 @@ -import unittest -import warnings -from datetime import timedelta - -from flask import Flask - -from flask_jwt_extended.config import config -from flask_jwt_extended import JWTManager - - -class TestEndpoints(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - JWTManager(self.app) - - def test_default_configs(self): - with self.app.test_request_context(): - self.assertEqual(config.token_location, ['headers']) - self.assertEqual(config.jwt_in_cookies, False) - self.assertEqual(config.jwt_in_headers, True) - self.assertEqual(config.header_name, 'Authorization') - self.assertEqual(config.header_type, 'Bearer') - - self.assertEqual(config.access_cookie_name, 'access_token_cookie') - self.assertEqual(config.refresh_cookie_name, 'refresh_token_cookie') - self.assertEqual(config.access_cookie_path, '/') - self.assertEqual(config.refresh_cookie_path, '/') - self.assertEqual(config.cookie_secure, False) - self.assertEqual(config.cookie_domain, None) - self.assertEqual(config.session_cookie, True) - - self.assertEqual(config.csrf_protect, False) - self.assertEqual(config.csrf_request_methods, ['POST', 'PUT', 'PATCH', 'DELETE']) - self.assertEqual(config.csrf_in_cookies, True) - self.assertEqual(config.access_csrf_cookie_name, 'csrf_access_token') - self.assertEqual(config.refresh_csrf_cookie_name, 'csrf_refresh_token') - self.assertEqual(config.access_csrf_cookie_path, '/') - self.assertEqual(config.refresh_csrf_cookie_path, '/') - self.assertEqual(config.access_csrf_header_name, 'X-CSRF-TOKEN') - self.assertEqual(config.refresh_csrf_header_name, 'X-CSRF-TOKEN') - - self.assertEqual(config.access_expires, timedelta(minutes=15)) - self.assertEqual(config.refresh_expires, timedelta(days=30)) - self.assertEqual(config.algorithm, 'HS256') - self.assertEqual(config.is_asymmetric, False) - self.assertEqual(config.blacklist_enabled, False) - self.assertEqual(config.blacklist_checks, ['access', 'refresh']) - self.assertEqual(config.blacklist_access_tokens, True) - self.assertEqual(config.blacklist_refresh_tokens, True) - - self.assertEqual(config.encode_key, self.app.secret_key) - self.assertEqual(config.decode_key, self.app.secret_key) - self.assertEqual(config.cookie_max_age, None) - - self.assertEqual(config.identity_claim, 'identity') - self.assertEqual(config.user_claims, 'user_claims') - - def test_override_configs(self): - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_HEADER_NAME'] = 'TestHeader' - self.app.config['JWT_HEADER_TYPE'] = 'TestType' - - self.app.config['JWT_ACCESS_COOKIE_NAME'] = 'new_access_cookie' - self.app.config['JWT_REFRESH_COOKIE_NAME'] = 'new_refresh_cookie' - self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/access/path' - self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/refresh/path' - self.app.config['JWT_COOKIE_SECURE'] = True - self.app.config['JWT_COOKIE_DOMAIN'] = ".example.com" - self.app.config['JWT_SESSION_COOKIE'] = False - - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.app.config['JWT_CSRF_METHODS'] = ['GET'] - self.app.config['JWT_CSRF_IN_COOKIES'] = False - self.app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'access_csrf_cookie' - self.app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'refresh_csrf_cookie' - self.app.config['JWT_ACCESS_CSRF_COOKIE_PATH'] = '/csrf/access/path' - self.app.config['JWT_REFRESH_CSRF_COOKIE_PATH'] = '/csrf/refresh/path' - self.app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'X-ACCESS-CSRF' - self.app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'X-REFRESH-CSRF' - - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5) - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=5) - self.app.config['JWT_ALGORITHM'] = 'HS512' - - self.app.config['JWT_BLACKLIST_ENABLED'] = True - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh' - - self.app.secret_key = 'banana' - - self.app.config['JWT_IDENTITY_CLAIM'] = 'foo' - self.app.config['JWT_USER_CLAIMS'] = 'bar' - - with self.app.test_request_context(): - self.assertEqual(config.token_location, ['cookies']) - self.assertEqual(config.jwt_in_cookies, True) - self.assertEqual(config.jwt_in_headers, False) - self.assertEqual(config.header_name, 'TestHeader') - self.assertEqual(config.header_type, 'TestType') - - self.assertEqual(config.access_cookie_name, 'new_access_cookie') - self.assertEqual(config.refresh_cookie_name, 'new_refresh_cookie') - self.assertEqual(config.access_cookie_path, '/access/path') - self.assertEqual(config.refresh_cookie_path, '/refresh/path') - self.assertEqual(config.cookie_secure, True) - self.assertEqual(config.cookie_domain, ".example.com") - self.assertEqual(config.session_cookie, False) - - self.assertEqual(config.csrf_protect, True) - self.assertEqual(config.csrf_request_methods, ['GET']) - self.assertEqual(config.csrf_in_cookies, False) - self.assertEqual(config.access_csrf_cookie_name, 'access_csrf_cookie') - self.assertEqual(config.refresh_csrf_cookie_name, 'refresh_csrf_cookie') - self.assertEqual(config.access_csrf_cookie_path, '/csrf/access/path') - self.assertEqual(config.refresh_csrf_cookie_path, '/csrf/refresh/path') - self.assertEqual(config.access_csrf_header_name, 'X-ACCESS-CSRF') - self.assertEqual(config.refresh_csrf_header_name, 'X-REFRESH-CSRF') - - self.assertEqual(config.access_expires, timedelta(minutes=5)) - self.assertEqual(config.refresh_expires, timedelta(days=5)) - self.assertEqual(config.algorithm, 'HS512') - - self.assertEqual(config.blacklist_enabled, True) - self.assertEqual(config.blacklist_checks, ['refresh']) - self.assertEqual(config.blacklist_access_tokens, False) - self.assertEqual(config.blacklist_refresh_tokens, True) - - self.assertEqual(config.cookie_max_age, 2147483647) - - self.assertEqual(config.identity_claim, 'foo') - self.assertEqual(config.user_claims, 'bar') - - def test_invalid_config_options(self): - with self.app.test_request_context(): - self.app.config['JWT_TOKEN_LOCATION'] = 'banana' - with self.assertRaises(RuntimeError): - config.token_location - - self.app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies', 'banana'] - with self.assertRaises(RuntimeError): - config.token_location - - self.app.config['JWT_HEADER_NAME'] = '' - with self.app.test_request_context(): - with self.assertRaises(RuntimeError): - config.header_name - - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 'banana' - with self.assertRaises(RuntimeError): - config.access_expires - - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 'banana' - with self.assertRaises(RuntimeError): - config.refresh_expires - - self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana' - with self.assertRaises(RuntimeError): - config.blacklist_checks - - self.app.secret_key = None - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.secret_key = '' - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.secret_key = None - with self.assertRaises(RuntimeError): - config.decode_key - - self.app.config['JWT_ALGORITHM'] = 'RS256' - self.app.config['JWT_PUBLIC_KEY'] = None - self.app.config['JWT_PRIVATE_KEY'] = None - with self.assertRaises(RuntimeError): - config.decode_key - with self.assertRaises(RuntimeError): - config.encode_key - - def test_depreciated_options(self): - self.app.config['JWT_CSRF_HEADER_NAME'] = 'Auth' - - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - - # Verify our warnings are thrown - with self.app.test_request_context(): - with warnings.catch_warnings(record=True) as w: - self.assertEqual(config.access_csrf_header_name, 'Auth') - self.assertEqual(config.refresh_csrf_header_name, 'Auth') - self.assertEqual(len(w), 2) - self.assertEqual(w[0].category, DeprecationWarning) - self.assertEqual(w[1].category, DeprecationWarning) - - def test_special_config_options(self): - with self.app.test_request_context(): - # Test changing strings to lists for JWT_TOKEN_LOCATIONS - self.app.config['JWT_TOKEN_LOCATION'] = 'headers' - self.assertEqual(config.token_location, ['headers']) - self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] - self.assertEqual(config.token_location, ['headers']) - self.app.config['JWT_TOKEN_LOCATION'] = 'cookies' - self.assertEqual(config.token_location, ['cookies']) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.assertEqual(config.token_location, ['cookies']) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] - self.assertEqual(config.token_location, ['cookies', 'headers']) - - # Test csrf protect options - self.app.config['JWT_TOKEN_LOCATION'] = ['headers'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.assertEqual(config.csrf_protect, False) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.assertEqual(config.csrf_protect, True) - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False - self.assertEqual(config.csrf_protect, False) - - def test_asymmetric_encryption_key_handling(self): - self.app.config['JWT_PRIVATE_KEY'] = 'MOCK_RSA_PRIVATE_KEY' - self.app.config['JWT_PUBLIC_KEY'] = 'MOCK_RSA_PUBLIC_KEY' - self.app.config['JWT_ALGORITHM'] = 'RS256' - - with self.app.test_request_context(): - self.assertEqual(config.is_asymmetric, True) - self.assertEqual(config.encode_key, 'MOCK_RSA_PRIVATE_KEY') - self.assertEqual(config.decode_key, 'MOCK_RSA_PUBLIC_KEY') diff --git a/tests_old/test_jwt_encode_decode.py b/tests_old/test_jwt_encode_decode.py deleted file mode 100644 index 73d166c3..00000000 --- a/tests_old/test_jwt_encode_decode.py +++ /dev/null @@ -1,409 +0,0 @@ -import unittest -import calendar -from datetime import datetime, timedelta - -import jwt -from flask import Flask -from flask_jwt_extended.exceptions import JWTDecodeError -from flask_jwt_extended.tokens import ( - encode_access_token, encode_refresh_token, - decode_jwt -) -from flask_jwt_extended.utils import create_access_token, create_refresh_token -from flask_jwt_extended.jwt_manager import JWTManager - - -class JWTEncodeDecodeTests(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.jwt = JWTManager(self.app) - - def test_jwt_identity(self): - pass - - def test_jwt_claims(self): - pass - - def test_encode_access_token(self): - secret = 'super-totally-secret-key' - algorithm = 'HS256' - token_expire_delta = timedelta(minutes=5) - user_claims = {'foo': 'bar'} - identity_claim = 'identity' - - # Check with a fresh token - with self.app.test_request_context(): - identity = 'user1' - token = encode_access_token(identity, secret, algorithm, token_expire_delta, - fresh=True, user_claims=user_claims, csrf=False, - identity_claim=identity_claim) - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn(identity_claim, data) - self.assertIn('fresh', data) - self.assertIn('type', data) - self.assertIn('user_claims', data) - self.assertNotIn('csrf', data) - self.assertEqual(data[identity_claim], identity) - self.assertEqual(data['fresh'], True) - self.assertEqual(data['type'], 'access') - self.assertEqual(data['user_claims'], user_claims) - self.assertEqual(data['iat'], data['nbf']) - now_ts = calendar.timegm(datetime.utcnow().utctimetuple()) - exp_seconds = data['exp'] - now_ts - self.assertLessEqual(exp_seconds, 60 * 5) - self.assertGreater(exp_seconds, 60 * 4) - - # Check with a non-fresh token - identity = 12345 # identity can be anything json serializable - token = encode_access_token(identity, secret, algorithm, token_expire_delta, - fresh=False, user_claims=user_claims, csrf=True, - identity_claim=identity_claim) - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn(identity_claim, data) - self.assertIn('fresh', data) - self.assertIn('type', data) - self.assertIn('user_claims', data) - self.assertIn('csrf', data) - self.assertEqual(data[identity_claim], identity) - self.assertEqual(data['fresh'], False) - self.assertEqual(data['type'], 'access') - self.assertEqual(data['user_claims'], user_claims) - self.assertEqual(data['iat'], data['nbf']) - now_ts = calendar.timegm(datetime.utcnow().utctimetuple()) - exp_seconds = data['exp'] - now_ts - self.assertLessEqual(exp_seconds, 60 * 5) - self.assertGreater(exp_seconds, 60 * 4) - - def test_encode_access_token__no_user_claims(self): - ''' - To make JWT shorter, do not add `user_claims` if empty. - ''' - secret = 'super-totally-secret-key' - algorithm = 'HS256' - token_expire_delta = timedelta(minutes=5) - identity_claim = 'sub' - - # `user_claims` is empty dict - with self.app.test_request_context(): - identity = 'user1' - token = encode_access_token(identity, secret, algorithm, token_expire_delta, - fresh=False, user_claims={}, csrf=False, - identity_claim=identity_claim) - - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertNotIn('user_claims', data) - - # `user_claims` is None - with self.app.test_request_context(): - identity = 'user1' - token = encode_access_token(identity, secret, algorithm, token_expire_delta, - fresh=False, user_claims=None, csrf=False, - identity_claim=identity_claim) - - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertNotIn('user_claims', data) - - def test_encode_invalid_access_token(self): - # Check with non-serializable json - with self.app.test_request_context(): - user_claims = datetime - identity_claim = 'identity' - with self.assertRaises(Exception): - encode_access_token('user1', 'secret', 'HS256', - timedelta(hours=1), True, user_claims, - csrf=True, identity_claim=identity_claim) - - user_claims = {'foo': timedelta(hours=4)} - with self.assertRaises(Exception): - encode_access_token('user1', 'secret', 'HS256', - timedelta(hours=1), True, user_claims, - csrf=True, identity_claim=identity_claim) - - def test_encode_refresh_token(self): - secret = 'super-totally-secret-key' - algorithm = 'HS256' - token_expire_delta = timedelta(minutes=5) - identity_claim = 'sub' - - # Check with a fresh token - with self.app.test_request_context(): - identity = 'user1' - token = encode_refresh_token(identity, secret, algorithm, - token_expire_delta, csrf=False, - identity_claim=identity_claim) - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn('type', data) - self.assertIn(identity_claim, data) - self.assertNotIn('csrf', data) - self.assertEqual(data[identity_claim], identity) - self.assertEqual(data['type'], 'refresh') - self.assertEqual(data['iat'], data['nbf']) - now_ts = calendar.timegm(datetime.utcnow().utctimetuple()) - exp_seconds = data['exp'] - now_ts - self.assertLessEqual(exp_seconds, 60 * 5) - self.assertGreater(exp_seconds, 60 * 4) - - # Check with a csrf token - identity = 12345 # identity can be anything json serializable - token = encode_refresh_token(identity, secret, algorithm, - token_expire_delta, csrf=True, - identity_claim=identity_claim) - data = jwt.decode(token, secret, algorithms=[algorithm]) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn('type', data) - self.assertIn('csrf', data) - self.assertIn(identity_claim, data) - self.assertEqual(data[identity_claim], identity) - self.assertEqual(data['type'], 'refresh') - self.assertEqual(data['iat'], data['nbf']) - now_ts = calendar.timegm(datetime.utcnow().utctimetuple()) - exp_seconds = data['exp'] - now_ts - self.assertLessEqual(exp_seconds, 60 * 5) - self.assertGreater(exp_seconds, 60 * 4) - - def test_decode_jwt(self): - identity_claim = 'sub' - # Test decoding a valid access token - with self.app.test_request_context(): - now = datetime.utcnow() - now_ts = calendar.timegm(now.utctimetuple()) - token_data = { - 'exp': now + timedelta(minutes=5), - 'iat': now, - 'nbf': now, - 'jti': 'banana', - identity_claim: 'banana', - 'fresh': True, - 'type': 'access', - 'user_claims': {'foo': 'bar'}, - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - data = decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn(identity_claim, data) - self.assertIn('fresh', data) - self.assertIn('type', data) - self.assertIn('user_claims', data) - self.assertEqual(data['exp'], now_ts + (5 * 60)) - self.assertEqual(data['iat'], now_ts) - self.assertEqual(data['nbf'], now_ts) - self.assertEqual(data['jti'], 'banana') - self.assertEqual(data[identity_claim], 'banana') - self.assertEqual(data['fresh'], True) - self.assertEqual(data['type'], 'access') - self.assertEqual(data['user_claims'], {'foo': 'bar'}) - - # Test decoding a valid refresh token - with self.app.test_request_context(): - now = datetime.utcnow() - now_ts = calendar.timegm(now.utctimetuple()) - token_data = { - 'exp': now + timedelta(minutes=5), - 'iat': now, - 'nbf': now, - 'jti': 'banana', - identity_claim: 'banana', - 'type': 'refresh', - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - data = decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn(identity_claim, data) - self.assertIn('type', data) - self.assertEqual(data['exp'], now_ts + (5 * 60)) - self.assertEqual(data['iat'], now_ts) - self.assertEqual(data['nbf'], now_ts) - self.assertEqual(data['jti'], 'banana') - self.assertEqual(data[identity_claim], 'banana') - self.assertEqual(data['type'], 'refresh') - - def test_decode_access_token__no_user_claims(self): - ''' - Test decoding a valid access token without `user_claims`. - ''' - identity_claim = 'sub' - with self.app.test_request_context(): - now = datetime.utcnow() - token_data = { - 'exp': now + timedelta(minutes=5), - 'iat': now, - 'nbf': now, - 'jti': 'banana', - identity_claim: 'banana', - 'fresh': True, - 'type': 'access', - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - data = decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - self.assertIn('user_claims', data) - self.assertEqual(data['user_claims'], {}) - - def test_decode_invalid_jwt(self): - with self.app.test_request_context(): - identity_claim = 'identity' - # Verify underlying pyjwt expires verification works - with self.assertRaises(jwt.ExpiredSignatureError): - token_data = { - 'exp': datetime.utcnow() - timedelta(minutes=5), - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Missing jti - with self.assertRaises(JWTDecodeError): - - token_data = { - 'exp': datetime.utcnow() + timedelta(minutes=5), - identity_claim: 'banana', - 'type': 'refresh' - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Missing identity - with self.assertRaises(JWTDecodeError): - token_data = { - 'jti': 'banana', - 'exp': datetime.utcnow() + timedelta(minutes=5), - 'type': 'refresh' - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Non-matching identity claim - with self.assertRaises(JWTDecodeError): - token_data = { - 'exp': datetime.utcnow() + timedelta(minutes=5), - identity_claim: 'banana', - 'type': 'refresh' - } - other_identity_claim = 'sub' - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - self.assertNotEqual(identity_claim, other_identity_claim) - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=other_identity_claim) - - # Missing type - with self.assertRaises(JWTDecodeError): - token_data = { - 'jti': 'banana', - identity_claim: 'banana', - 'exp': datetime.utcnow() + timedelta(minutes=5), - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Missing fresh in access token - with self.assertRaises(JWTDecodeError): - token_data = { - 'jti': 'banana', - identity_claim: 'banana', - 'exp': datetime.utcnow() + timedelta(minutes=5), - 'type': 'access', - 'user_claims': {} - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Bad token type - with self.assertRaises(JWTDecodeError): - token_data = { - 'jti': 'banana', - identity_claim: 'banana', - 'exp': datetime.utcnow() + timedelta(minutes=5), - 'type': 'banana', - 'fresh': True, - 'user_claims': 'banana' - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', - csrf=False, identity_claim=identity_claim) - - # Missing csrf in csrf enabled token - with self.assertRaises(JWTDecodeError): - token_data = { - 'jti': 'banana', - identity_claim: 'banana', - 'exp': datetime.utcnow() + timedelta(minutes=5), - 'type': 'access', - 'fresh': True, - 'user_claims': 'banana' - } - encoded_token = jwt.encode(token_data, 'secret', 'HS256').decode('utf-8') - decode_jwt(encoded_token, 'secret', 'HS256', csrf=True, - identity_claim=identity_claim) - - def test_create_jwt_with_object(self): - # Complex object to test building a JWT from. Normally if you are using - # this functionality, this is something that would be retrieved from - # disk somewhere (think sqlalchemy) - class TestUser: - def __init__(self, username, roles): - self.username = username - self.roles = roles - - # Setup the flask stuff - app = Flask(__name__) - app.secret_key = 'super=secret' - app.config['JWT_ALGORITHM'] = 'HS256' - jwt = JWTManager(app) - - @jwt.user_claims_loader - def custom_claims(user): - return { - 'roles': user.roles - } - - @jwt.user_identity_loader - def user_identity_lookup(user): - return user.username - - # Create the token using the complex object - with app.test_request_context(): - identity_claim = 'sub' - app.config['JWT_IDENTITY_CLAIM'] = identity_claim - user = TestUser(username='foo', roles=['bar', 'baz']) - access_token = create_access_token(identity=user) - refresh_token = create_refresh_token(identity=user) - - # Decode the tokens and make sure the values are set properly - access_token_data = decode_jwt(access_token, app.secret_key, - app.config['JWT_ALGORITHM'], csrf=False, - identity_claim=identity_claim) - refresh_token_data = decode_jwt(refresh_token, app.secret_key, - app.config['JWT_ALGORITHM'], csrf=False, - identity_claim=identity_claim) - self.assertEqual(access_token_data[identity_claim], 'foo') - self.assertEqual(access_token_data['user_claims']['roles'], ['bar', 'baz']) - self.assertEqual(refresh_token_data[identity_claim], 'foo') diff --git a/tests_old/test_jwt_manager.py b/tests_old/test_jwt_manager.py deleted file mode 100644 index 106a026a..00000000 --- a/tests_old/test_jwt_manager.py +++ /dev/null @@ -1,237 +0,0 @@ -import unittest -import json - -from flask import Flask, jsonify -from flask_jwt_extended import JWTManager -from flask_jwt_extended.utils import has_user_loader - - -class TestJWTManager(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - - def _parse_callback_result(self, result): - """ - Returns a tuple, where the first item is http status code and - the second is the data (via json.loads) - """ - response = result[0] - status_code = result[1] - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_init_app(self): - jwt_manager = JWTManager() - jwt_manager.init_app(self.app) - self.assertEqual(jwt_manager, self.app.extensions['flask-jwt-extended']) - - def test_class_init(self): - jwt_manager = JWTManager(self.app) - self.assertEqual(jwt_manager, self.app.extensions['flask-jwt-extended']) - - def test_default_user_claims_callback(self): - identity = 'foobar' - m = JWTManager(self.app) - self.assertEqual(m._user_claims_callback(identity), {}) - - def test_default_user_identity_callback(self): - identity = 'foobar' - m = JWTManager(self.app) - self.assertEqual(m._user_identity_callback(identity), identity) - - def test_default_expired_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - result = m._expired_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Token has expired'}) - - def test_default_invalid_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - err = "Test error" - result = m._invalid_token_callback(err) - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 422) - self.assertEqual(data, {'msg': err}) - - def test_default_unauthorized_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - result = m._unauthorized_callback("Missing Authorization Header") - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Missing Authorization Header'}) - - def test_default_needs_fresh_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - result = m._needs_fresh_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Fresh token required'}) - - def test_default_revoked_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - result = m._revoked_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Token has been revoked'}) - - def test_default_user_loader_callback(self): - m = JWTManager(self.app) - self.assertEqual(m._user_loader_callback, None) - - def test_default_user_loader_error_callback(self): - with self.app.test_request_context(): - identity = 'foobar' - m = JWTManager(self.app) - result = m._user_loader_error_callback(identity) - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - def test_default_has_user_loader(self): - m = JWTManager(self.app) - with self.app.app_context(): - self.assertEqual(has_user_loader(), False) - - def test_custom_user_claims_callback(self): - identity = 'foobar' - m = JWTManager(self.app) - - @m.user_claims_loader - def custom_user_claims(identity): - return {'foo': 'bar'} - - assert m._user_claims_callback(identity) == {'foo': 'bar'} - - def test_custom_expired_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.expired_token_loader - def custom_expired_token(): - return jsonify({"res": "TOKEN IS EXPIRED FOOL"}), 422 - - result = m._expired_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 422) - self.assertEqual(data, {'res': 'TOKEN IS EXPIRED FOOL'}) - - def test_custom_invalid_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - err = "Test error" - - @m.invalid_token_loader - def custom_invalid_token(err): - return jsonify({"err": err}), 200 - - result = m._invalid_token_callback(err) - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 200) - self.assertEqual(data, {'err': err}) - - def test_custom_unauthorized_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.unauthorized_loader - def custom_unauthorized(err_str): - return jsonify({"err": err_str}), 200 - - result = m._unauthorized_callback("GOTTA LOGIN FOOL") - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 200) - self.assertEqual(data, {'err': 'GOTTA LOGIN FOOL'}) - - def test_custom_needs_fresh_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.needs_fresh_token_loader - def custom_token_needs_refresh(): - return jsonify({'sub_status': 101}), 200 - - result = m._needs_fresh_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 200) - self.assertEqual(data, {'sub_status': 101}) - - def test_custom_revoked_token_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.revoked_token_loader - def custom_revoken_token(): - return jsonify({"err": "Nice knowing you!"}), 422 - result = m._revoked_token_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 422) - self.assertEqual(data, {'err': 'Nice knowing you!'}) - - def test_custom_user_loader(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.user_loader_callback_loader - def custom_user_loader(identity): - if identity == 'foo': - return None - return identity - - identity = 'foobar' - result = m._user_loader_callback(identity) - self.assertEqual(result, identity) - self.assertEqual(has_user_loader(), True) - - def test_custom_user_loader_error_callback(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.user_loader_error_loader - def custom_user_loader_error(identity): - return jsonify({'msg': 'Not found'}), 404 - - identity = 'foobar' - result = m._user_loader_error_callback(identity) - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - def test_claims_verification(self): - with self.app.test_request_context(): - m = JWTManager(self.app) - - @m.claims_verification_loader - def user_claims_verification(claims): - return 'foo' in claims - - @m.claims_verification_failed_loader - def user_claims_verification_failed(): - return jsonify({'msg': 'Test'}), 404 - - result = m._claims_verification_callback({'bar': 'baz'}) - self.assertEqual(result, False) - - result = m._claims_verification_failed_callback() - status_code, data = self._parse_callback_result(result) - - self.assertEqual(status_code, 404) - self.assertEqual(data, {'msg': 'Test'}) diff --git a/tests_old/test_protected_endpoints.py b/tests_old/test_protected_endpoints.py deleted file mode 100644 index 032b3cd3..00000000 --- a/tests_old/test_protected_endpoints.py +++ /dev/null @@ -1,1138 +0,0 @@ -import json -import time -import unittest -from datetime import datetime, timedelta - -from flask import Flask, jsonify -import jwt - -from flask_jwt_extended.tokens import encode_access_token -from flask_jwt_extended.utils import get_jwt_claims, \ - get_jwt_identity, set_refresh_cookies, set_access_cookies, unset_jwt_cookies -from flask_jwt_extended import JWTManager, create_refresh_token, \ - jwt_refresh_token_required, create_access_token, fresh_jwt_required, \ - jwt_optional, jwt_required, get_raw_jwt - - -class TestEndpoints(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.app.config['JWT_ALGORITHM'] = 'HS256' - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(seconds=1) - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(seconds=1) - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.app.route('/auth/login', methods=['POST']) - def login(): - ret = { - 'access_token': create_access_token('test', fresh=True), - 'refresh_token': create_refresh_token('test') - } - return jsonify(ret), 200 - - @self.app.route('/auth/login2', methods=['POST']) - def login2(): - expires = timedelta(minutes=5) - ret = { - 'access_token': create_access_token('test', fresh=True, - expires_delta=expires), - 'refresh_token': create_refresh_token('test', expires_delta=expires), - } - return jsonify(ret), 200 - - @self.app.route('/auth/refresh', methods=['POST']) - @jwt_refresh_token_required - def refresh(): - username = get_jwt_identity() - ret = {'access_token': create_access_token(username, fresh=False)} - return jsonify(ret), 200 - - @self.app.route('/auth/fresh-login', methods=['POST']) - def fresh_login(): - ret = {'access_token': create_access_token('test', fresh=True)} - return jsonify(ret), 200 - - @self.app.route('/protected') - @jwt_required - def protected(): - return jsonify({'msg': "hello world"}) - - @self.app.route('/fresh-protected') - @fresh_jwt_required - def fresh_protected(): - return jsonify({'msg': "fresh hello world"}) - - @self.app.route('/partially-protected') - @jwt_optional - def partially_protected(): - if get_jwt_identity(): - return jsonify({'msg': "protected hello world"}) - return jsonify({'msg': "unprotected hello world"}) - - def _jwt_post(self, url, jwt): - response = self.client.post(url, content_type='application/json', - headers={'Authorization': 'Bearer {}'.format(jwt)}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def _jwt_get(self, url, jwt, header_name='Authorization', header_type='Bearer'): - header_type = '{} {}'.format(header_type, jwt).strip() - response = self.client.get(url, headers={header_name: header_type}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_login(self): - response = self.client.post('/auth/login') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertIn('refresh_token', data) - - def test_fresh_login(self): - response = self.client.post('/auth/fresh-login') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('refresh_token', data) - - def test_refresh(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('refresh_token', data) - - def test_wrong_token_refresh(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - # Try to refresh with an access token instead of a refresh one - status_code, data = self._jwt_post('/auth/refresh', access_token) - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - def test_jwt_required(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - fresh_access_token = data['access_token'] - refresh_token = data['refresh_token'] - - # Test it works with a fresh token - status, data = self._jwt_get('/protected', fresh_access_token) - self.assertEqual(data, {'msg': 'hello world'}) - self.assertEqual(status, 200) - - # Test it works with a non-fresh access token - _, data = self._jwt_post('/auth/refresh', refresh_token) - non_fresh_token = data['access_token'] - status, data = self._jwt_get('/protected', non_fresh_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - def test_jwt_optional_no_jwt(self): - response = self.client.get('/partially-protected') - data = json.loads(response.get_data(as_text=True)) - status = response.status_code - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'unprotected hello world'}) - - def test_jwt_optional_with_jwt(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - fresh_access_token = data['access_token'] - refresh_token = data['refresh_token'] - - # Test it works with a fresh token - status, data = self._jwt_get('/partially-protected', - fresh_access_token) - self.assertEqual(data, {'msg': 'protected hello world'}) - self.assertEqual(status, 200) - - # Test it works with a non-fresh access token - _, data = self._jwt_post('/auth/refresh', refresh_token) - non_fresh_token = data['access_token'] - status, data = self._jwt_get('/partially-protected', non_fresh_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'protected hello world'}) - - def test_jwt_required_wrong_token(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - refresh_token = data['refresh_token'] - - # Shouldn't work with a refresh token - status, text = self._jwt_get('/protected', refresh_token) - self.assertEqual(status, 422) - - def test_jwt_optional_wrong_token(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - refresh_token = data['refresh_token'] - - # Shouldn't work with a refresh token - status, text = self._jwt_get('/partially-protected', refresh_token) - self.assertEqual(status, 422) - - def test_fresh_jwt_required(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - fresh_access_token = data['access_token'] - refresh_token = data['refresh_token'] - - # Test it works with a fresh token - status, data = self._jwt_get('/fresh-protected', fresh_access_token) - self.assertEqual(data, {'msg': 'fresh hello world'}) - self.assertEqual(status, 200) - - # Test it works with a non-fresh access token - _, data = self._jwt_post('/auth/refresh', refresh_token) - non_fresh_token = data['access_token'] - status, text = self._jwt_get('/fresh-protected', non_fresh_token) - self.assertEqual(status, 401) - - def test_fresh_jwt_required_wrong_token(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - refresh_token = data['refresh_token'] - - # Shouldn't work with a refresh token - status, text = self._jwt_get('/fresh-protected', refresh_token) - self.assertEqual(status, 422) - - def test_without_secret_key(self): - app = Flask(__name__) - app.testing = True # Propagate exceptions - JWTManager(app) - client = app.test_client() - - @app.route('/login', methods=['POST']) - def login(): - ret = {'access_token': create_access_token('test', fresh=True)} - return jsonify(ret), 200 - - with self.assertRaises(RuntimeError): - client.post('/login') - - def test_bad_jwt_requests(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - # Test with no authorization header - response = self.client.get('/protected') - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Test with missing type in authorization header - auth_header = access_token - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test with type not being Bearer in authorization header - auth_header = "BANANA {}".format(access_token) - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test with too many items in auth header - auth_header = "Bearer {} BANANA".format(access_token) - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - def test_optional_bad_jwt_requests(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - # Test with missing type in authorization header - auth_header = access_token - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(data, {'msg': 'unprotected hello world'}) - self.assertEqual(status_code, 200) - - # Test with type not being Bearer in authorization header - auth_header = "BANANA {}".format(access_token) - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(data, {'msg': 'unprotected hello world'}) - self.assertEqual(status_code, 200) - - # Test with too many items in auth header - auth_header = "Bearer {} BANANA".format(access_token) - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(data, {'msg': 'unprotected hello world'}) - self.assertEqual(status_code, 200) - - def test_bad_tokens(self): - # Test expired access token - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - status_code, data = self._jwt_get('/protected', access_token) - self.assertEqual(status_code, 200) - self.assertIn('msg', data) - time.sleep(2) - status_code, data = self._jwt_get('/protected', access_token) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Test expired refresh token - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - refresh_token = data['refresh_token'] - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('msg', data) - time.sleep(2) - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 401) - self.assertNotIn('access_token', data) - self.assertIn('msg', data) - - # Test Bogus token - auth_header = "Bearer {}".format('this_is_totally_an_access_token') - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test token that was signed with a different key - with self.app.test_request_context(): - token = encode_access_token('foo', 'newsecret', 'HS256', - timedelta(minutes=5), True, {}, csrf=False, - identity_claim='identity') - auth_header = "Bearer {}".format(token) - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test with valid token that is missing required claims - now = datetime.utcnow() - token_data = {'exp': now + timedelta(minutes=5)} - encoded_token = jwt.encode(token_data, self.app.config['SECRET_KEY'], - self.app.config['JWT_ALGORITHM']).decode('utf-8') - auth_header = "Bearer {}".format(encoded_token) - response = self.client.get('/protected', headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - def test_expires_time_override(self): - # Test access token - response = self.client.post('/auth/login2') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - time.sleep(2) - status_code, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'protected hello world'}) - - # Test refresh token - response = self.client.post('/auth/login2') - data = json.loads(response.get_data(as_text=True)) - refresh_token = data['refresh_token'] - time.sleep(2) - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('msg', data) - - def test_optional_jwt_bad_tokens(self): - # Test expired access token - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - status_code, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'protected hello world'}) - time.sleep(2) - status_code, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Test Bogus token - auth_header = "Bearer {}".format('this_is_totally_an_access_token') - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test token that was signed with a different key - with self.app.test_request_context(): - token = encode_access_token('foo', 'newsecret', 'HS256', - timedelta(minutes=5), True, {}, - csrf=False, identity_claim='identity') - auth_header = "Bearer {}".format(token) - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - # Test with valid token that is missing required claims - now = datetime.utcnow() - token_data = {'exp': now + timedelta(minutes=5)} - encoded_token = jwt.encode(token_data, self.app.config['SECRET_KEY'], - self.app.config['JWT_ALGORITHM']).decode('utf-8') - auth_header = "Bearer {}".format(encoded_token) - response = self.client.get('/partially-protected', - headers={'Authorization': auth_header}) - data = json.loads(response.get_data(as_text=True)) - status_code = response.status_code - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - def test_jwt_identity_claims(self): - # Setup custom claims - @self.jwt_manager.user_claims_loader - def custom_claims(identity): - return {'foo': 'bar'} - - @self.app.route('/claims') - @jwt_required - def claims(): - return jsonify({ - 'username': get_jwt_identity(), - 'claims': get_jwt_claims() - }) - - - # Login - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - # Test our custom endpoint - status, data = self._jwt_get('/claims', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test', 'claims': {'foo': 'bar'}}) - - def test_jwt_raw_token(self): - # Endpoints that uses get raw tokens and returns the keys - @self.app.route('/claims') - @jwt_required - def claims(): - jwt = get_raw_jwt() - claims_keys = [claim for claim in jwt] - return jsonify(claims_keys), 200 - - # Grab custom identity claim - identity_claim = self.app.config['JWT_IDENTITY_CLAIM'] - - # Login - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - # Test our custom endpoint - status, data = self._jwt_get('/claims', access_token) - self.assertEqual(status, 200) - self.assertIn('exp', data) - self.assertIn('iat', data) - self.assertIn('nbf', data) - self.assertIn('jti', data) - self.assertIn(identity_claim, data) - self.assertIn('fresh', data) - self.assertIn('type', data) - self.assertIn('user_claims', data) - - def test_different_headers(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - self.app.config['JWT_HEADER_TYPE'] = 'JWT' - status, data = self._jwt_get('/protected', access_token, header_type='JWT') - self.assertEqual(data, {'msg': 'hello world'}) - self.assertEqual(status, 200) - - self.app.config['JWT_HEADER_TYPE'] = '' - status, data = self._jwt_get('/protected', access_token, header_type='') - self.assertEqual(data, {'msg': 'hello world'}) - self.assertEqual(status, 200) - - self.app.config['JWT_HEADER_TYPE'] = '' - status, data = self._jwt_get('/protected', access_token, header_type='Bearer') - self.assertIn('msg', data) - self.assertEqual(status, 422) - - self.app.config['JWT_HEADER_TYPE'] = 'Bearer' - self.app.config['JWT_HEADER_NAME'] = 'Auth' - status, data = self._jwt_get('/protected', access_token, header_name='Auth', - header_type='Bearer') - self.assertIn('msg', data) - self.assertEqual(status, 200) - - status, data = self._jwt_get('/protected', access_token, header_name='Authorization', - header_type='Bearer') - self.assertIn('msg', data) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Missing Auth Header'}) - - def test_different_headers_jwt_optional(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - self.app.config['JWT_HEADER_TYPE'] = 'JWT' - status, data = self._jwt_get('/partially-protected', access_token, - header_type='JWT') - self.assertEqual(data, {'msg': 'protected hello world'}) - self.assertEqual(status, 200) - - self.app.config['JWT_HEADER_TYPE'] = '' - status, data = self._jwt_get('/partially-protected', access_token, - header_type='') - self.assertEqual(data, {'msg': 'protected hello world'}) - self.assertEqual(status, 200) - - self.app.config['JWT_HEADER_TYPE'] = '' - status, data = self._jwt_get('/partially-protected', access_token, - header_type='Bearer') - self.assertEqual(data, {'msg': 'unprotected hello world'}) - self.assertEqual(status, 200) - - self.app.config['JWT_HEADER_TYPE'] = 'Bearer' - self.app.config['JWT_HEADER_NAME'] = 'Auth' - status, data = self._jwt_get('/partially-protected', access_token, - header_name='Auth', header_type='Bearer') - self.assertEqual(data, {'msg': 'protected hello world'}) - self.assertEqual(status, 200) - - status, data = self._jwt_get('/partially-protected', access_token, - header_name='Authorization', - header_type='Bearer') - self.assertEqual(status, 200) - self.assertEqual(data, {'msg': 'unprotected hello world'}) - - def test_cookie_methods_fail_with_headers_configured(self): - app = Flask(__name__) - app.config['JWT_TOKEN_LOCATION'] = ['headers'] - app.secret_key = 'super=secret' - app.testing = True - JWTManager(app) - client = app.test_client() - - @app.route('/login-bad', methods=['POST']) - def bad_login(): - access_token = create_access_token('test') - resp = jsonify({'login': True}) - set_access_cookies(resp, access_token) - return resp, 200 - - @app.route('/refresh-bad', methods=['POST']) - def bad_refresh(): - refresh_token = create_refresh_token('test') - resp = jsonify({'login': True}) - set_refresh_cookies(resp, refresh_token) - return resp, 200 - - @app.route('/logout-bad', methods=['POST']) - def bad_logout(): - resp = jsonify({'logout': True}) - unset_jwt_cookies(resp) - return resp, 200 - - with self.assertRaises(RuntimeWarning): - client.post('/login-bad') - with self.assertRaises(RuntimeWarning): - client.post('/refresh-bad') - with self.assertRaises(RuntimeWarning): - client.post('/logout-bad') - - def test_jwt_with_different_algorithm(self): - self.app.config['JWT_ALGORITHM'] = 'HS256' - self.app.secret_key = 'test_secret' - access_token = encode_access_token( - identity='bobdobbs', - secret='test_secret', - algorithm='HS512', - expires_delta=timedelta(minutes=5), - fresh=True, - user_claims={}, - csrf=False, - identity_claim='identity' - ) - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 422) - self.assertIn('msg', data) - - def test_optional_jwt_with_different_algorithm(self): - self.app.config['JWT_ALGORITHM'] = 'HS256' - self.app.secret_key = 'test_secret' - access_token = encode_access_token( - identity='bobdobbs', - secret='test_secret', - algorithm='HS512', - expires_delta=timedelta(minutes=5), - fresh=True, - user_claims={}, - csrf=False, - identity_claim='identity' - ) - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 422) - self.assertIn('msg', data) - - -class TestEndpointsWithCookies(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.app.config['JWT_TOKEN_LOCATION'] = 'cookies' - self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/api/' - self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/auth/refresh' - self.app.config['JWT_ACCESS_COOKIE_NAME'] = 'access_token_cookie' - self.app.config['JWT_ALGORITHM'] = 'HS256' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.app.route('/auth/login', methods=['POST']) - def login(): - # Create the tokens we will be sending back to the user - access_token = create_access_token(identity='test') - refresh_token = create_refresh_token(identity='test') - - # Set the JWTs and the CSRF double submit protection cookies in this response - resp = jsonify({'login': True}) - set_access_cookies(resp, access_token) - set_refresh_cookies(resp, refresh_token) - return resp, 200 - - @self.app.route('/auth/logout', methods=['POST']) - def logout(): - resp = jsonify({'logout': True}) - unset_jwt_cookies(resp) - return resp, 200 - - @self.app.route('/auth/refresh', methods=['POST']) - @jwt_refresh_token_required - def refresh(): - username = get_jwt_identity() - access_token = create_access_token(username, fresh=False) - resp = jsonify({'refresh': True}) - set_access_cookies(resp, access_token) - return resp, 200 - - @self.app.route('/api/protected', methods=['POST']) - @jwt_required - def protected(): - return jsonify({'msg': "hello world"}) - - def _login(self): - resp = self.client.post('/auth/login') - index = 1 - - access_cookie_str = resp.headers[index][1] - access_cookie_key = access_cookie_str.split('=')[0] - access_cookie_value = "".join(access_cookie_str.split('=')[1:]) - self.client.set_cookie('localhost', access_cookie_key, access_cookie_value) - index += 1 - - if self.app.config['JWT_COOKIE_CSRF_PROTECT']: - access_csrf_str = resp.headers[index][1] - access_csrf_key = access_csrf_str.split('=')[0] - access_csrf_value = "".join(access_csrf_str.split('=')[1:]) - self.client.set_cookie('localhost', access_csrf_key, access_csrf_value) - index += 1 - access_csrf = access_csrf_value.split(';')[0] - else: - access_csrf = "" - - refresh_cookie_str = resp.headers[index][1] - refresh_cookie_key = refresh_cookie_str.split('=')[0] - refresh_cookie_value = "".join(refresh_cookie_str.split('=')[1:]) - self.client.set_cookie('localhost', refresh_cookie_key, refresh_cookie_value) - index += 1 - - if self.app.config['JWT_COOKIE_CSRF_PROTECT']: - refresh_csrf_str = resp.headers[index][1] - refresh_csrf_key = refresh_csrf_str.split('=')[0] - refresh_csrf_value = "".join(refresh_csrf_str.split('=')[1:]) - self.client.set_cookie('localhost', refresh_csrf_key, refresh_csrf_value) - refresh_csrf = refresh_csrf_value.split(';')[0] - else: - refresh_csrf = "" - - return access_csrf, refresh_csrf - - def test_headers(self): - # Try with default options - resp = self.client.post('/auth/login') - access_cookie = resp.headers[1][1] - access_csrf = resp.headers[2][1] - refresh_cookie = resp.headers[3][1] - refresh_csrf = resp.headers[4][1] - self.assertIn('access_token_cookie', access_cookie) - self.assertIn('csrf_access_token', access_csrf) - self.assertIn('Path=/', access_csrf) - self.assertIn('refresh_token_cookie', refresh_cookie) - self.assertIn('csrf_refresh_token', refresh_csrf) - self.assertIn('Path=/', refresh_csrf) - - # Try with overwritten options - self.app.config['JWT_ACCESS_COOKIE_NAME'] = 'new_access_cookie' - self.app.config['JWT_REFRESH_COOKIE_NAME'] = 'new_refresh_cookie' - self.app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'x_csrf_access_token' - self.app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'x_csrf_refresh_token' - self.app.config['JWT_ACCESS_COOKIE_PATH'] = None - self.app.config['JWT_REFRESH_COOKIE_PATH'] = None - - resp = self.client.post('/auth/login') - access_cookie = resp.headers[1][1] - access_csrf = resp.headers[2][1] - refresh_cookie = resp.headers[3][1] - refresh_csrf = resp.headers[4][1] - self.assertIn('new_access_cookie', access_cookie) - self.assertIn('x_csrf_access_token', access_csrf) - self.assertIn('Path=/', access_csrf) - self.assertIn('new_refresh_cookie', refresh_cookie) - self.assertIn('x_csrf_refresh_token', refresh_csrf) - self.assertIn('Path=/', refresh_csrf) - - # Try logout headers - resp = self.client.post('/auth/logout') - refresh_cookie = resp.headers[1][1] - access_cookie = resp.headers[2][1] - self.assertIn('Expires=Thu, 01-Jan-1970', refresh_cookie) - self.assertIn('Expires=Thu, 01-Jan-1970', access_cookie) - - def test_endpoints_with_cookies(self): - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False - - # Try access without logging in - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Try refresh without logging in - response = self.client.post('/auth/refresh') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Try with logging in - self._login() - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - # Try refresh without logging in - response = self.client.post('/auth/refresh') - access_cookie_str = response.headers[1][1] - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertIn('access_token_cookie', access_cookie_str) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'refresh': True}) - - # Try accessing endpoint with newly refreshed token - access_cookie_key = access_cookie_str.split('=')[0] - access_cookie_value = "".join(access_cookie_str.split('=')[1:]) - self.client.set_cookie('localhost', access_cookie_key, access_cookie_value) - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - def test_access_endpoints_with_cookies_and_csrf(self): - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - - # Try without logging in - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Login - access_csrf, refresh_csrf = self._login() - - # Try with logging in but without double submit csrf protection - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Try with logged in and bad header name for double submit token - response = self.client.post('/api/protected', - headers={'bad-header-name': 'banana'}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Try with logged in and bad header data for double submit token - response = self.client.post('/api/protected', - headers={'X-CSRF-TOKEN': 'banana'}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - # Try with logged in and good double submit token - response = self.client.post('/api/protected', - headers={'X-CSRF-TOKEN': access_csrf}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - def test_access_endpoints_with_cookie_missing_csrf_field(self): - # Test accessing a csrf protected endpoint with a cookie that does not - # have a csrf token in it - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False - self._login() - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 422) - self.assertIn('msg', data) - - def test_access_endpoints_with_cookie_csrf_claim_not_string(self): - now = datetime.utcnow() - identity_claim = self.app.config['JWT_IDENTITY_CLAIM'] - token_data = { - 'exp': now + timedelta(minutes=5), - 'iat': now, - 'nbf': now, - 'jti': 'banana', - identity_claim: 'banana', - 'type': 'refresh', - 'csrf': 404 - } - secret = self.app.secret_key - algorithm = self.app.config['JWT_ALGORITHM'] - encoded_token = jwt.encode(token_data, secret, algorithm).decode('utf-8') - access_cookie_key = self.app.config['JWT_ACCESS_COOKIE_NAME'] - self.client.set_cookie('localhost', access_cookie_key, encoded_token) - - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - response = self.client.post('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - def test_custom_csrf_methods(self): - @self.app.route('/protected-post', methods=['POST']) - @jwt_required - def protected_post(): - return jsonify({'msg': "hello world"}) - - @self.app.route('/protected-get', methods=['GET']) - @jwt_required - def protected_get(): - return jsonify({'msg': "hello world"}) - - # Login (saves jwts in the cookies for the test client - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self._login() - - # Test being able to access GET without CSRF protection, and POST with - # CSRF protection - self.app.config['JWT_CSRF_METHODS'] = ['POST'] - - response = self.client.post('/protected-post') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - response = self.client.get('/protected-get') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - # Now swap it around, and verify the JWT_CRSF_METHODS are being honored - self.app.config['JWT_CSRF_METHODS'] = ['GET'] - - response = self.client.get('/protected-get') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - response = self.client.post('/protected-post') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - -class TestEndpointsWithHeadersAndCookies(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'headers'] - self.app.config['JWT_COOKIE_CSRF_PROTECT'] = True - self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/api/' - self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/auth/refresh' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.app.route('/auth/login_cookies', methods=['POST']) - def login_cookies(): - # Create the tokens we will be sending back to the user - access_token = create_access_token(identity='test') - refresh_token = create_refresh_token(identity='test') - - # Set the JWTs and the CSRF double submit protection cookies in this response - resp = jsonify({'login': True}) - set_access_cookies(resp, access_token) - set_refresh_cookies(resp, refresh_token) - return resp, 200 - - @self.app.route('/auth/login_headers', methods=['POST']) - def login_headers(): - ret = { - 'access_token': create_access_token('test', fresh=True), - 'refresh_token': create_refresh_token('test') - } - return jsonify(ret), 200 - - @self.app.route('/api/protected') - @jwt_required - def protected(): - return jsonify({'msg': "hello world"}) - - def _jwt_post(self, url, jwt): - response = self.client.post(url, content_type='application/json', - headers={'Authorization': 'Bearer {}'.format(jwt)}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def _jwt_get(self, url, jwt, header_name='Authorization', header_type='Bearer'): - header_type = '{} {}'.format(header_type, jwt).strip() - response = self.client.get(url, headers={header_name: header_type}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def _login_cookies(self): - resp = self.client.post('/auth/login_cookies') - index = 1 - - access_cookie_str = resp.headers[index][1] - access_cookie_key = access_cookie_str.split('=')[0] - access_cookie_value = "".join(access_cookie_str.split('=')[1:]) - self.client.set_cookie('localhost', access_cookie_key, access_cookie_value) - index += 1 - - if self.app.config['JWT_COOKIE_CSRF_PROTECT']: - access_csrf_str = resp.headers[index][1] - access_csrf_key = access_csrf_str.split('=')[0] - access_csrf_value = "".join(access_csrf_str.split('=')[1:]) - self.client.set_cookie('localhost', access_csrf_key, access_csrf_value) - index += 1 - access_csrf = access_csrf_value.split(';')[0] - else: - access_csrf = "" - - refresh_cookie_str = resp.headers[index][1] - refresh_cookie_key = refresh_cookie_str.split('=')[0] - refresh_cookie_value = "".join(refresh_cookie_str.split('=')[1:]) - self.client.set_cookie('localhost', refresh_cookie_key, refresh_cookie_value) - index += 1 - - if self.app.config['JWT_COOKIE_CSRF_PROTECT']: - refresh_csrf_str = resp.headers[index][1] - refresh_csrf_key = refresh_csrf_str.split('=')[0] - refresh_csrf_value = "".join(refresh_csrf_str.split('=')[1:]) - self.client.set_cookie('localhost', refresh_csrf_key, refresh_csrf_value) - refresh_csrf = refresh_csrf_value.split(';')[0] - else: - refresh_csrf = "" - - return access_csrf, refresh_csrf - - def _login_headers(self): - resp = self.client.post('/auth/login_headers') - data = json.loads(resp.get_data(as_text=True)) - return data['access_token'], data['refresh_token'] - - def test_accessing_endpoint_with_headers(self): - access_token, _ = self._login_headers() - header_type = '{} {}'.format('Bearer', access_token).strip() - response = self.client.get('/api/protected', headers={'Authorization': header_type}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - def test_accessing_endpoint_with_cookies(self): - access_csrf, _ = self._login_cookies() - response = self.client.get('/api/protected', - headers={'X-CSRF-TOKEN': access_csrf}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertEqual(data, {'msg': 'hello world'}) - - def test_accessing_endpoint_without_jwt(self): - response = self.client.get('/api/protected') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 401) - self.assertIn('msg', data) - - -# random 1024bit RSA keypair -RSA_PRIVATE = """ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP -DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo -xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB -AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 -NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D -G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H -bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ -Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ -tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 -cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA -mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv -lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d -E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== ------END RSA PRIVATE KEY----- -""" -RSA_PUBLIC = """ ------BEGIN RSA PUBLIC KEY----- -MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd -E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 -mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= ------END RSA PUBLIC KEY----- -""" - -class TestEndpointsWithAssymmetricCrypto(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.config['JWT_PUBLIC_KEY'] = RSA_PUBLIC - self.app.config['JWT_PRIVATE_KEY'] = RSA_PRIVATE - self.app.config['JWT_ALGORITHM'] = 'RS256' - self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(seconds=1) - self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(seconds=1) - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.app.route('/auth/login', methods=['POST']) - def login(): - ret = { - 'access_token': create_access_token('test', fresh=True), - 'refresh_token': create_refresh_token('test') - } - return jsonify(ret), 200 - - @self.app.route('/auth/refresh', methods=['POST']) - @jwt_refresh_token_required - def refresh(): - username = get_jwt_identity() - ret = {'access_token': create_access_token(username, fresh=False)} - return jsonify(ret), 200 - - @self.app.route('/auth/fresh-login', methods=['POST']) - def fresh_login(): - ret = {'access_token': create_access_token('test', fresh=True)} - return jsonify(ret), 200 - - @self.app.route('/protected') - @jwt_required - def protected(): - return jsonify({'msg': "hello world"}) - - @self.app.route('/fresh-protected') - @fresh_jwt_required - def fresh_protected(): - return jsonify({'msg': "fresh hello world"}) - - def _jwt_post(self, url, jwt): - response = self.client.post(url, content_type='application/json', - headers={'Authorization': 'Bearer {}'.format(jwt)}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def _jwt_get(self, url, jwt, header_name='Authorization', header_type='Bearer'): - header_type = '{} {}'.format(header_type, jwt).strip() - response = self.client.get(url, headers={header_name: header_type}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_login(self): - response = self.client.post('/auth/login') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertIn('refresh_token', data) - - def test_fresh_login(self): - response = self.client.post('/auth/fresh-login') - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('refresh_token', data) - - def test_refresh(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status_code, data = self._jwt_post('/auth/refresh', refresh_token) - self.assertEqual(status_code, 200) - self.assertIn('access_token', data) - self.assertNotIn('refresh_token', data) diff --git a/tests_old/test_user_claims_verification.py b/tests_old/test_user_claims_verification.py deleted file mode 100644 index b81561ac..00000000 --- a/tests_old/test_user_claims_verification.py +++ /dev/null @@ -1,87 +0,0 @@ -import unittest - -from flask import Flask, jsonify, json - -from flask_jwt_extended import JWTManager, create_access_token, jwt_required - - -class TestUserClaimsVerification(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.jwt_manager.claims_verification_loader - def claims_verification(user_claims): - expected_keys = ['foo', 'bar'] - for key in expected_keys: - if key not in user_claims: - return False - return True - - @self.app.route('/auth/login', methods=['POST']) - def login(): - ret = {'access_token': create_access_token('test')} - return jsonify(ret), 200 - - @self.app.route('/protected') - @jwt_required - def protected(): - return jsonify({'msg': "hello world"}) - - def _jwt_get(self, url, jwt, header_name='Authorization', header_type='Bearer'): - header_type = '{} {}'.format(header_type, jwt).strip() - response = self.client.get(url, headers={header_name: header_type}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_valid_user_claims(self): - @self.jwt_manager.user_claims_loader - def user_claims_callback(identity): - return {'foo': 'baz', 'bar': 'boom'} - - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(data, {'msg': 'hello world'}) - self.assertEqual(status, 200) - - def test_empty_claims_verification_error(self): - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(data, {'msg': 'User claims verification failed'}) - self.assertEqual(status, 400) - - def test_bad_claims_verification_error(self): - @self.jwt_manager.user_claims_loader - def user_claims_callback(identity): - return {'super': 'banana'} - - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(data, {'msg': 'User claims verification failed'}) - self.assertEqual(status, 400) - - def test_bad_claims_custom_error_callback(self): - @self.jwt_manager.claims_verification_failed_loader - def user_claims_callback(): - return jsonify({'foo': 'bar'}), 404 - - response = self.client.post('/auth/login') - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(data, {'foo': 'bar'}) - self.assertEqual(status, 404) diff --git a/tests_old/test_user_loader.py b/tests_old/test_user_loader.py deleted file mode 100644 index 33e27ac1..00000000 --- a/tests_old/test_user_loader.py +++ /dev/null @@ -1,135 +0,0 @@ -import json -import unittest -from datetime import timedelta - -from flask import Flask, jsonify, request - -from flask_jwt_extended import ( - JWTManager, create_access_token, create_refresh_token, - jwt_refresh_token_required, jwt_required, fresh_jwt_required, - jwt_optional, current_user -) - - -class TestUserLoader(unittest.TestCase): - - def setUp(self): - self.app = Flask(__name__) - self.app.secret_key = 'super=secret' - self.jwt_manager = JWTManager(self.app) - self.client = self.app.test_client() - - @self.jwt_manager.user_loader_callback_loader - def user_loader(identity): - if identity == 'foobar': - return None - return identity - - @self.app.route('/auth/login', methods=['POST']) - def login(): - username = request.get_json()['username'] - ret = { - 'access_token': create_access_token(username, fresh=True), - 'refresh_token': create_refresh_token(username) - } - return jsonify(ret), 200 - - @self.app.route('/refresh-protected') - @jwt_refresh_token_required - def refresh_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/protected') - @jwt_required - def protected_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/fresh-protected') - @fresh_jwt_required - def fresh_protected_endpoint(): - return jsonify({'username': str(current_user)}) - - @self.app.route('/partially-protected') - @jwt_optional - def optional_endpoint(): - return jsonify({'username': str(current_user)}) - - def _jwt_get(self, url, jwt): - response = self.client.get(url, content_type='application/json', - headers={'Authorization': 'Bearer {}'.format(jwt)}) - status_code = response.status_code - data = json.loads(response.get_data(as_text=True)) - return status_code, data - - def test_user_loads(self): - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'test'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 200) - self.assertEqual(data, {'username': 'test'}) - - def test_failed_user_loads(self): - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'foobar'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 401) - self.assertEqual(data, {'msg': 'Error loading the user foobar'}) - - def test_custom_error_callback(self): - @self.jwt_manager.user_loader_error_loader - def custom_user_loader_error_callback(identity): - return jsonify({"msg": "Not found"}), 404 - - response = self.client.post('/auth/login', content_type='application/json', - data=json.dumps({'username': 'foobar'})) - data = json.loads(response.get_data(as_text=True)) - access_token = data['access_token'] - refresh_token = data['refresh_token'] - - status, data = self._jwt_get('/protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/fresh-protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/partially-protected', access_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) - - status, data = self._jwt_get('/refresh-protected', refresh_token) - self.assertEqual(status, 404) - self.assertEqual(data, {'msg': 'Not found'}) From e6edccc1b0fe5af7d9819b54c9c248782c7ff6d3 Mon Sep 17 00:00:00 2001 From: Landon GB Date: Mon, 23 Oct 2017 13:52:47 -0600 Subject: [PATCH 18/18] Fix tox.ini --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index a6c27b85..33ff6574 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ commands = coverage run --source flask_jwt_extended -m pytest tests/ coverage report -m deps = + pytest coverage cryptography # TODO why does this not work?