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