1from datetime import timedelta
2
3import pytest
4from dateutil.relativedelta import relativedelta
5from flask import Flask
6from flask.json import JSONEncoder
7
8from flask_jwt_extended import JWTManager
9from flask_jwt_extended.config import config
10
11
12@pytest.fixture(scope="function")
13def app():
14    app = Flask(__name__)
15    JWTManager(app)
16    return app
17
18
19def test_default_configs(app):
20    with app.test_request_context():
21        assert config.token_location == ("headers",)
22        assert config.jwt_in_query_string is False
23        assert config.jwt_in_cookies is False
24        assert config.jwt_in_json is False
25        assert config.jwt_in_headers is True
26
27        assert config.encode_issuer is None
28        assert config.decode_issuer is None
29
30        assert config.header_name == "Authorization"
31        assert config.header_type == "Bearer"
32
33        assert config.query_string_name == "jwt"
34        assert config.query_string_value_prefix == ""
35
36        assert config.access_cookie_name == "access_token_cookie"
37        assert config.refresh_cookie_name == "refresh_token_cookie"
38        assert config.access_cookie_path == "/"
39        assert config.refresh_cookie_path == "/"
40        assert config.cookie_secure is False
41        assert config.cookie_domain is None
42        assert config.session_cookie is True
43        assert config.cookie_samesite is None
44
45        assert config.json_key == "access_token"
46        assert config.refresh_json_key == "refresh_token"
47
48        assert config.csrf_protect is False
49        assert config.csrf_request_methods == ["POST", "PUT", "PATCH", "DELETE"]
50        assert config.csrf_in_cookies is True
51        assert config.access_csrf_cookie_name == "csrf_access_token"
52        assert config.refresh_csrf_cookie_name == "csrf_refresh_token"
53        assert config.access_csrf_cookie_path == "/"
54        assert config.refresh_csrf_cookie_path == "/"
55        assert config.access_csrf_header_name == "X-CSRF-TOKEN"
56        assert config.refresh_csrf_header_name == "X-CSRF-TOKEN"
57
58        assert config.access_expires == timedelta(minutes=15)
59        assert config.refresh_expires == timedelta(days=30)
60        assert config.algorithm == "HS256"
61        assert config.decode_algorithms == ["HS256"]
62        assert config.is_asymmetric is False
63
64        assert config.cookie_max_age is None
65
66        assert config.identity_claim_key == "sub"
67
68        assert config.json_encoder is app.json_encoder
69
70        assert config.error_msg_key == "msg"
71
72
73@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
74def test_override_configs(app, delta_func):
75    app.config["JWT_TOKEN_LOCATION"] = ["cookies", "query_string", "json"]
76    app.config["JWT_HEADER_NAME"] = "TestHeader"
77    app.config["JWT_HEADER_TYPE"] = "TestType"
78    app.config["JWT_JSON_KEY"] = "TestKey"
79    app.config["JWT_REFRESH_JSON_KEY"] = "TestRefreshKey"
80
81    app.config["JWT_DECODE_ISSUER"] = "TestDecodeIssuer"
82    app.config["JWT_ENCODE_ISSUER"] = "TestEncodeIssuer"
83
84    app.config["JWT_QUERY_STRING_NAME"] = "banana"
85    app.config["JWT_QUERY_STRING_VALUE_PREFIX"] = "kiwi"
86
87    app.config["JWT_ACCESS_COOKIE_NAME"] = "new_access_cookie"
88    app.config["JWT_REFRESH_COOKIE_NAME"] = "new_refresh_cookie"
89    app.config["JWT_ACCESS_COOKIE_PATH"] = "/access/path"
90    app.config["JWT_REFRESH_COOKIE_PATH"] = "/refresh/path"
91    app.config["JWT_COOKIE_SECURE"] = True
92    app.config["JWT_COOKIE_DOMAIN"] = ".example.com"
93    app.config["JWT_SESSION_COOKIE"] = False
94    app.config["JWT_COOKIE_SAMESITE"] = "Strict"
95
96    app.config["JWT_COOKIE_CSRF_PROTECT"] = True
97    app.config["JWT_CSRF_METHODS"] = ["GET"]
98    app.config["JWT_CSRF_IN_COOKIES"] = False
99    app.config["JWT_ACCESS_CSRF_COOKIE_NAME"] = "access_csrf_cookie"
100    app.config["JWT_REFRESH_CSRF_COOKIE_NAME"] = "refresh_csrf_cookie"
101    app.config["JWT_ACCESS_CSRF_COOKIE_PATH"] = "/csrf/access/path"
102    app.config["JWT_REFRESH_CSRF_COOKIE_PATH"] = "/csrf/refresh/path"
103    app.config["JWT_ACCESS_CSRF_HEADER_NAME"] = "X-ACCESS-CSRF"
104    app.config["JWT_REFRESH_CSRF_HEADER_NAME"] = "X-REFRESH-CSRF"
105
106    app.config["JWT_ACCESS_TOKEN_EXPIRES"] = delta_func(minutes=5)
107    app.config["JWT_REFRESH_TOKEN_EXPIRES"] = delta_func(days=5)
108    app.config["JWT_ALGORITHM"] = "HS512"
109    app.config["JWT_DECODE_ALGORITHMS"] = ["HS512", "HS256"]
110
111    app.config["JWT_IDENTITY_CLAIM"] = "foo"
112
113    app.config["JWT_ERROR_MESSAGE_KEY"] = "message"
114
115    class CustomJSONEncoder(JSONEncoder):
116        pass
117
118    app.json_encoder = CustomJSONEncoder
119
120    with app.test_request_context():
121        assert config.token_location == ["cookies", "query_string", "json"]
122        assert config.jwt_in_query_string is True
123        assert config.jwt_in_cookies is True
124        assert config.jwt_in_headers is False
125        assert config.jwt_in_json is True
126        assert config.header_name == "TestHeader"
127        assert config.header_type == "TestType"
128        assert config.json_key == "TestKey"
129        assert config.refresh_json_key == "TestRefreshKey"
130
131        assert config.decode_issuer == "TestDecodeIssuer"
132        assert config.encode_issuer == "TestEncodeIssuer"
133
134        assert config.query_string_name == "banana"
135        assert config.query_string_value_prefix == "kiwi"
136
137        assert config.access_cookie_name == "new_access_cookie"
138        assert config.refresh_cookie_name == "new_refresh_cookie"
139        assert config.access_cookie_path == "/access/path"
140        assert config.refresh_cookie_path == "/refresh/path"
141        assert config.cookie_secure is True
142        assert config.cookie_domain == ".example.com"
143        assert config.session_cookie is False
144        assert config.cookie_samesite == "Strict"
145
146        assert config.csrf_protect is True
147        assert config.csrf_request_methods == ["GET"]
148        assert config.csrf_in_cookies is False
149        assert config.access_csrf_cookie_name == "access_csrf_cookie"
150        assert config.refresh_csrf_cookie_name == "refresh_csrf_cookie"
151        assert config.access_csrf_cookie_path == "/csrf/access/path"
152        assert config.refresh_csrf_cookie_path == "/csrf/refresh/path"
153        assert config.access_csrf_header_name == "X-ACCESS-CSRF"
154        assert config.refresh_csrf_header_name == "X-REFRESH-CSRF"
155
156        assert config.access_expires == delta_func(minutes=5)
157        assert config.refresh_expires == delta_func(days=5)
158        assert config.algorithm == "HS512"
159        assert config.decode_algorithms == ["HS512", "HS256"]
160
161        assert config.cookie_max_age == 31540000
162
163        assert config.identity_claim_key == "foo"
164
165        assert config.json_encoder is CustomJSONEncoder
166
167        assert config.error_msg_key == "message"
168
169
170def test_tokens_never_expire(app):
171    app.config["JWT_ACCESS_TOKEN_EXPIRES"] = False
172    app.config["JWT_REFRESH_TOKEN_EXPIRES"] = False
173    with app.test_request_context():
174        assert config.access_expires is False
175        assert config.refresh_expires is False
176
177
178def test_tokens_with_int_values(app):
179    app.config["JWT_ACCESS_TOKEN_EXPIRES"] = 300
180    app.config["JWT_REFRESH_TOKEN_EXPIRES"] = 432000
181
182    with app.test_request_context():
183        assert config.access_expires == timedelta(minutes=5)
184        assert config.refresh_expires == timedelta(days=5)
185
186
187# noinspection PyStatementEffect
188def test_symmetric_secret_key(app):
189    with app.test_request_context():
190        assert config.is_asymmetric is False
191
192        with pytest.raises(RuntimeError):
193            config.encode_key
194        with pytest.raises(RuntimeError):
195            config.decode_key
196
197        app.secret_key = "foobar"
198        with app.test_request_context():
199            assert config.encode_key == "foobar"
200            assert config.decode_key == "foobar"
201
202        app.config["JWT_SECRET_KEY"] = "foobarbaz"
203        with app.test_request_context():
204            assert config.encode_key == "foobarbaz"
205            assert config.decode_key == "foobarbaz"
206
207
208# noinspection PyStatementEffect
209def test_default_with_asymmetric_secret_key(app):
210    with app.test_request_context():
211        app.config["JWT_ALGORITHM"] = "RS256"
212        assert config.is_asymmetric is True
213
214        # If no key is entered, should raise an error
215        with pytest.raises(RuntimeError):
216            config.encode_key
217        with pytest.raises(RuntimeError):
218            config.decode_key
219
220        # Make sure the secret key isn't being used for asymmetric stuff
221        app.secret_key = "foobar"
222        with pytest.raises(RuntimeError):
223            config.encode_key
224        with pytest.raises(RuntimeError):
225            config.decode_key
226
227        # Make sure the secret key isn't being used for asymmetric stuff
228        app.config["JWT_SECRET_KEY"] = "foobarbaz"
229        with pytest.raises(RuntimeError):
230            config.encode_key
231        with pytest.raises(RuntimeError):
232            config.decode_key
233
234        app.config["JWT_PUBLIC_KEY"] = "foo2"
235        app.config["JWT_PRIVATE_KEY"] = "bar2"
236        app.config["JWT_ALGORITHM"] = "RS256"
237        with app.test_request_context():
238            assert config.decode_key == "foo2"
239            assert config.encode_key == "bar2"
240
241
242# noinspection PyStatementEffect
243def test_invalid_config_options(app):
244    with app.test_request_context():
245        app.config["JWT_TOKEN_LOCATION"] = []
246        with pytest.raises(RuntimeError):
247            config.token_location
248
249        app.config["JWT_TOKEN_LOCATION"] = "banana"
250        with pytest.raises(RuntimeError):
251            config.token_location
252
253        app.config["JWT_TOKEN_LOCATION"] = 1
254        with pytest.raises(RuntimeError):
255            config.token_location
256
257        app.config["JWT_TOKEN_LOCATION"] = {"location": "headers"}
258        with pytest.raises(RuntimeError):
259            config.token_location
260
261        app.config["JWT_TOKEN_LOCATION"] = range(99)
262        with pytest.raises(RuntimeError):
263            config.token_location
264
265        app.config["JWT_TOKEN_LOCATION"] = ["headers", "cookies", "banana"]
266        with pytest.raises(RuntimeError):
267            config.token_location
268
269        app.config["JWT_HEADER_NAME"] = ""
270        with app.test_request_context():
271            with pytest.raises(RuntimeError):
272                config.header_name
273
274        app.config["JWT_ACCESS_TOKEN_EXPIRES"] = "banana"
275        with pytest.raises(RuntimeError):
276            config.access_expires
277
278        app.config["JWT_REFRESH_TOKEN_EXPIRES"] = "banana"
279        with pytest.raises(RuntimeError):
280            config.refresh_expires
281
282        app.config["JWT_ACCESS_TOKEN_EXPIRES"] = True
283        with pytest.raises(RuntimeError):
284            config.access_expires
285
286        app.config["JWT_REFRESH_TOKEN_EXPIRES"] = True
287        with pytest.raises(RuntimeError):
288            config.refresh_expires
289
290
291def test_jwt_token_locations_config(app):
292    with app.test_request_context():
293        allowed_locations = ("headers", "cookies", "query_string", "json")
294        allowed_data_structures = (tuple, list, frozenset, set)
295
296        for location in allowed_locations:
297            app.config["JWT_TOKEN_LOCATION"] = location
298            assert config.token_location == (location,)
299
300        for locations in (
301            data_structure((location,))
302            for data_structure in allowed_data_structures
303            for location in allowed_locations
304        ):
305            app.config["JWT_TOKEN_LOCATION"] = locations
306            assert config.token_location == locations
307
308        for locations in (
309            data_structure(allowed_locations[:i])
310            for data_structure in allowed_data_structures
311            for i in range(1, len(allowed_locations))
312        ):
313            app.config["JWT_TOKEN_LOCATION"] = locations
314            assert config.token_location == locations
315
316
317def test_csrf_protect_config(app):
318    with app.test_request_context():
319        app.config["JWT_TOKEN_LOCATION"] = ["headers"]
320        app.config["JWT_COOKIE_CSRF_PROTECT"] = True
321        assert config.csrf_protect is False
322
323        app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
324        app.config["JWT_COOKIE_CSRF_PROTECT"] = True
325        assert config.csrf_protect is True
326
327        app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
328        app.config["JWT_COOKIE_CSRF_PROTECT"] = False
329        assert config.csrf_protect is False
330
331
332def test_missing_algorithm_in_decode_algorithms(app):
333    app.config["JWT_ALGORITHM"] = "RS256"
334    app.config["JWT_DECODE_ALGORITHMS"] = ["HS512"]
335
336    with app.test_request_context():
337        assert config.decode_algorithms == ["HS512", "RS256"]
338