Format: text/x-python
⬇ Download File (9.8 KB)
   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