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