1 from datetime import datetime 2 from datetime import timedelta 3 from datetime import timezone 4 from json import JSONEncoder 5 from typing import Iterable 6 from typing import List 7 from typing import Optional 8 from typing import Sequence 9 from typing import Type 10 from typing import Union 11 12 from flask import current_app 13 from jwt.algorithms import requires_cryptography 14 15 from flask_jwt_extended.internal_utils import get_json_encoder 16 from flask_jwt_extended.typing import ExpiresDelta 17 18 19 class _Config(object): 20 """ 21 Helper object for accessing and verifying options in this extension. This 22 is meant for internal use of the application; modifying config options 23 should be done with flasks ```app.config```. 24 25 Default values for the configuration options are set in the jwt_manager 26 object. All of these values are read only. This is simply a loose wrapper 27 with some helper functionality for flasks `app.config`. 28 """ 29 30 @property 31 def is_asymmetric(self) -> bool: 32 return self.algorithm in requires_cryptography 33 34 @property 35 def encode_key(self) -> str: 36 return self._private_key if self.is_asymmetric else self._secret_key 37 38 @property 39 def decode_key(self) -> str: 40 return self._public_key if self.is_asymmetric else self._secret_key 41 42 @property 43 def token_location(self) -> Sequence[str]: 44 locations = current_app.config["JWT_TOKEN_LOCATION"] 45 if isinstance(locations, str): 46 locations = (locations,) 47 elif not isinstance(locations, Iterable): 48 raise RuntimeError("JWT_TOKEN_LOCATION must be a sequence or a set") 49 elif not locations: 50 raise RuntimeError( 51 "JWT_TOKEN_LOCATION must contain at least one " 52 'of "headers", "cookies", "query_string", or "json"' 53 ) 54 for location in locations: 55 if location not in ("headers", "cookies", "query_string", "json"): 56 raise RuntimeError( 57 "JWT_TOKEN_LOCATION can only contain " 58 '"headers", "cookies", "query_string", or "json"' 59 ) 60 return locations 61 62 @property 63 def jwt_in_cookies(self) -> bool: 64 return "cookies" in self.token_location 65 66 @property 67 def jwt_in_headers(self) -> bool: 68 return "headers" in self.token_location 69 70 @property 71 def jwt_in_query_string(self) -> bool: 72 return "query_string" in self.token_location 73 74 @property 75 def jwt_in_json(self) -> bool: 76 return "json" in self.token_location 77 78 @property 79 def header_name(self) -> str: 80 name = current_app.config["JWT_HEADER_NAME"] 81 if not name: 82 raise RuntimeError("JWT_ACCESS_HEADER_NAME cannot be empty") 83 return name 84 85 @property 86 def header_type(self) -> str: 87 return current_app.config["JWT_HEADER_TYPE"] 88 89 @property 90 def query_string_name(self) -> str: 91 return current_app.config["JWT_QUERY_STRING_NAME"] 92 93 @property 94 def query_string_value_prefix(self) -> str: 95 return current_app.config["JWT_QUERY_STRING_VALUE_PREFIX"] 96 97 @property 98 def access_cookie_name(self) -> str: 99 return current_app.config["JWT_ACCESS_COOKIE_NAME"] 100 101 @property 102 def refresh_cookie_name(self) -> str: 103 return current_app.config["JWT_REFRESH_COOKIE_NAME"] 104 105 @property 106 def access_cookie_path(self) -> str: 107 return current_app.config["JWT_ACCESS_COOKIE_PATH"] 108 109 @property 110 def refresh_cookie_path(self) -> str: 111 return current_app.config["JWT_REFRESH_COOKIE_PATH"] 112 113 @property 114 def cookie_secure(self) -> bool: 115 return current_app.config["JWT_COOKIE_SECURE"] 116 117 @property 118 def cookie_domain(self) -> str: 119 return current_app.config["JWT_COOKIE_DOMAIN"] 120 121 @property 122 def session_cookie(self) -> bool: 123 return current_app.config["JWT_SESSION_COOKIE"] 124 125 @property 126 def cookie_samesite(self) -> str: 127 return current_app.config["JWT_COOKIE_SAMESITE"] 128 129 @property 130 def json_key(self) -> str: 131 return current_app.config["JWT_JSON_KEY"] 132 133 @property 134 def refresh_json_key(self) -> str: 135 return current_app.config["JWT_REFRESH_JSON_KEY"] 136 137 @property 138 def csrf_protect(self) -> bool: 139 return self.jwt_in_cookies and current_app.config["JWT_COOKIE_CSRF_PROTECT"] 140 141 @property 142 def csrf_request_methods(self) -> Iterable[str]: 143 return current_app.config["JWT_CSRF_METHODS"] 144 145 @property 146 def csrf_in_cookies(self) -> bool: 147 return current_app.config["JWT_CSRF_IN_COOKIES"] 148 149 @property 150 def access_csrf_cookie_name(self) -> str: 151 return current_app.config["JWT_ACCESS_CSRF_COOKIE_NAME"] 152 153 @property 154 def refresh_csrf_cookie_name(self) -> str: 155 return current_app.config["JWT_REFRESH_CSRF_COOKIE_NAME"] 156 157 @property 158 def access_csrf_cookie_path(self) -> str: 159 return current_app.config["JWT_ACCESS_CSRF_COOKIE_PATH"] 160 161 @property 162 def refresh_csrf_cookie_path(self) -> str: 163 return current_app.config["JWT_REFRESH_CSRF_COOKIE_PATH"] 164 165 @property 166 def access_csrf_header_name(self) -> str: 167 return current_app.config["JWT_ACCESS_CSRF_HEADER_NAME"] 168 169 @property 170 def refresh_csrf_header_name(self) -> str: 171 return current_app.config["JWT_REFRESH_CSRF_HEADER_NAME"] 172 173 @property 174 def csrf_check_form(self) -> bool: 175 return current_app.config["JWT_CSRF_CHECK_FORM"] 176 177 @property 178 def access_csrf_field_name(self) -> str: 179 return current_app.config["JWT_ACCESS_CSRF_FIELD_NAME"] 180 181 @property 182 def refresh_csrf_field_name(self) -> str: 183 return current_app.config["JWT_REFRESH_CSRF_FIELD_NAME"] 184 185 @property 186 def access_expires(self) -> ExpiresDelta: 187 delta = current_app.config["JWT_ACCESS_TOKEN_EXPIRES"] 188 if type(delta) is int: 189 delta = timedelta(seconds=delta) 190 if delta is not False: 191 try: 192 # Basically runtime typechecking. Probably a better way to do 193 # this with proper type checking 194 delta + datetime.now(timezone.utc) 195 except TypeError as e: 196 err = ( 197 "must be able to add JWT_ACCESS_TOKEN_EXPIRES to datetime.datetime" 198 ) 199 raise RuntimeError(err) from e 200 return delta 201 202 @property 203 def refresh_expires(self) -> ExpiresDelta: 204 delta = current_app.config["JWT_REFRESH_TOKEN_EXPIRES"] 205 if type(delta) is int: 206 delta = timedelta(seconds=delta) 207 if delta is not False: 208 # Basically runtime typechecking. Probably a better way to do 209 # this with proper type checking 210 try: 211 delta + datetime.now(timezone.utc) 212 except TypeError as e: 213 err = ( 214 "must be able to add JWT_REFRESH_TOKEN_EXPIRES to datetime.datetime" 215 ) 216 raise RuntimeError(err) from e 217 return delta 218 219 @property 220 def algorithm(self) -> str: 221 return current_app.config["JWT_ALGORITHM"] 222 223 @property 224 def decode_algorithms(self) -> List[str]: 225 algorithms = current_app.config["JWT_DECODE_ALGORITHMS"] 226 if not algorithms: 227 return [self.algorithm] 228 if self.algorithm not in algorithms: 229 algorithms.append(self.algorithm) 230 return algorithms 231 232 @property 233 def _secret_key(self) -> str: 234 key = current_app.config["JWT_SECRET_KEY"] 235 if not key: 236 key = current_app.config.get("SECRET_KEY", None) 237 if not key: 238 raise RuntimeError( 239 "JWT_SECRET_KEY or flask SECRET_KEY " 240 "must be set when using symmetric " 241 'algorithm "{}"'.format(self.algorithm) 242 ) 243 return key 244 245 @property 246 def _public_key(self) -> str: 247 key = current_app.config["JWT_PUBLIC_KEY"] 248 if not key: 249 raise RuntimeError( 250 "JWT_PUBLIC_KEY must be set to use " 251 "asymmetric cryptography algorithm " 252 '"{}"'.format(self.algorithm) 253 ) 254 return key 255 256 @property 257 def _private_key(self) -> str: 258 key = current_app.config["JWT_PRIVATE_KEY"] 259 if not key: 260 raise RuntimeError( 261 "JWT_PRIVATE_KEY must be set to use " 262 "asymmetric cryptography algorithm " 263 '"{}"'.format(self.algorithm) 264 ) 265 return key 266 267 @property 268 def cookie_max_age(self) -> Optional[int]: 269 # Returns the appropiate value for max_age for flask set_cookies. If 270 # session cookie is true, return None, otherwise return a number of 271 # seconds 1 year in the future 272 return None if self.session_cookie else 31540000 # 1 year 273 274 @property 275 def identity_claim_key(self) -> str: 276 return current_app.config["JWT_IDENTITY_CLAIM"] 277 278 @property 279 def exempt_methods(self) -> Iterable[str]: 280 return {"OPTIONS"} 281 282 @property 283 def error_msg_key(self) -> str: 284 return current_app.config["JWT_ERROR_MESSAGE_KEY"] 285 286 @property 287 def json_encoder(self) -> Type[JSONEncoder]: 288 return get_json_encoder(current_app) 289 290 @property 291 def decode_audience(self) -> Union[str, Iterable[str]]: 292 return current_app.config["JWT_DECODE_AUDIENCE"] 293 294 @property 295 def encode_audience(self) -> Union[str, Iterable[str]]: 296 return current_app.config["JWT_ENCODE_AUDIENCE"] 297 298 @property 299 def encode_issuer(self) -> str: 300 return current_app.config["JWT_ENCODE_ISSUER"] 301 302 @property 303 def decode_issuer(self) -> str: 304 return current_app.config["JWT_DECODE_ISSUER"] 305 306 @property 307 def leeway(self) -> int: 308 return current_app.config["JWT_DECODE_LEEWAY"] 309 310 @property 311 def encode_nbf(self) -> bool: 312 return current_app.config["JWT_ENCODE_NBF"] 313 314 315 config = _Config() 316