plain 0.4.1__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/cli/cli.py +36 -13
- plain/csrf/middleware.py +23 -40
- plain/forms/fields.py +2 -3
- plain/forms/forms.py +2 -1
- plain/http/request.py +5 -5
- plain/internal/handlers/base.py +13 -1
- plain/internal/middleware/headers.py +19 -0
- plain/internal/middleware/https.py +36 -0
- plain/{middleware/common.py → internal/middleware/slash.py} +2 -25
- plain/preflight/security/base.py +5 -174
- plain/preflight/security/csrf.py +1 -5
- plain/runtime/README.md +0 -4
- plain/runtime/__init__.py +13 -15
- plain/runtime/global_settings.py +38 -55
- plain/runtime/user_settings.py +226 -217
- plain/signing.py +5 -23
- plain/test/client.py +17 -17
- plain/utils/timezone.py +2 -23
- plain/views/base.py +4 -0
- {plain-0.4.1.dist-info → plain-0.11.0.dist-info}/METADATA +2 -2
- {plain-0.4.1.dist-info → plain-0.11.0.dist-info}/RECORD +25 -27
- {plain-0.4.1.dist-info → plain-0.11.0.dist-info}/WHEEL +1 -1
- plain/middleware/README.md +0 -3
- plain/middleware/clickjacking.py +0 -52
- plain/middleware/gzip.py +0 -64
- plain/middleware/security.py +0 -64
- /plain/{middleware → internal/middleware}/__init__.py +0 -0
- {plain-0.4.1.dist-info → plain-0.11.0.dist-info}/LICENSE +0 -0
- {plain-0.4.1.dist-info → plain-0.11.0.dist-info}/entry_points.txt +0 -0
plain/runtime/user_settings.py
CHANGED
@@ -1,13 +1,5 @@
|
|
1
|
-
"""
|
2
|
-
Settings and configuration for Plain.
|
3
|
-
|
4
|
-
Read values from the module specified by the PLAIN_SETTINGS_MODULE environment
|
5
|
-
variable, and then from plain.global_settings; see the global_settings.py
|
6
|
-
for a list of all possible variables.
|
7
|
-
"""
|
8
1
|
import importlib
|
9
2
|
import json
|
10
|
-
import logging
|
11
3
|
import os
|
12
4
|
import time
|
13
5
|
import types
|
@@ -16,106 +8,256 @@ from pathlib import Path
|
|
16
8
|
|
17
9
|
from plain.exceptions import ImproperlyConfigured
|
18
10
|
from plain.packages import PackageConfig
|
19
|
-
from plain.utils.functional import LazyObject, empty
|
20
11
|
|
21
12
|
ENVIRONMENT_VARIABLE = "PLAIN_SETTINGS_MODULE"
|
22
13
|
ENV_SETTINGS_PREFIX = "PLAIN_"
|
23
|
-
|
24
|
-
logger = logging.getLogger("plain.runtime")
|
14
|
+
CUSTOM_SETTINGS_PREFIX = "APP_"
|
25
15
|
|
26
16
|
|
27
|
-
class
|
17
|
+
class Settings:
|
28
18
|
"""
|
29
|
-
|
30
|
-
|
19
|
+
Settings and configuration for Plain.
|
20
|
+
|
21
|
+
This class handles loading settings from the module specified by the
|
22
|
+
PLAIN_SETTINGS_MODULE environment variable, as well as from default settings,
|
23
|
+
environment variables, and explicit settings in the settings module.
|
24
|
+
|
25
|
+
Lazy initialization is implemented to defer loading until settings are first accessed.
|
31
26
|
"""
|
32
27
|
|
33
|
-
def
|
34
|
-
|
28
|
+
def __init__(self, settings_module=None):
|
29
|
+
self._settings_module = settings_module
|
30
|
+
self._settings = {}
|
31
|
+
self._errors = [] # Collect configuration errors
|
32
|
+
self.configured = False
|
35
33
|
|
36
|
-
def
|
37
|
-
self.
|
34
|
+
def _setup(self):
|
35
|
+
if self.configured:
|
36
|
+
return
|
37
|
+
else:
|
38
|
+
self.configured = True
|
38
39
|
|
40
|
+
self._settings = {} # Maps setting names to SettingDefinition instances
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
# Determine the settings module
|
43
|
+
if self._settings_module is None:
|
44
|
+
self._settings_module = os.environ.get(ENVIRONMENT_VARIABLE, "app.settings")
|
45
|
+
|
46
|
+
# First load the global settings from plain
|
47
|
+
self._load_module_settings(
|
48
|
+
importlib.import_module("plain.runtime.global_settings")
|
49
|
+
)
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
self._wrapped = Settings(settings_module)
|
51
|
+
# Import the user's settings module
|
52
|
+
try:
|
53
|
+
mod = importlib.import_module(self._settings_module)
|
54
|
+
except ImportError as e:
|
55
|
+
raise ImproperlyConfigured(
|
56
|
+
f"Could not import settings '{self._settings_module}': {e}"
|
57
|
+
)
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
# Keep a reference to the settings.py module path
|
60
|
+
self.path = Path(mod.__file__).resolve()
|
61
|
+
|
62
|
+
# Load default settings from installed packages
|
63
|
+
self._load_default_settings(mod)
|
64
|
+
# Load environment settings
|
65
|
+
self._load_env_settings()
|
66
|
+
# Load explicit settings from the settings module
|
67
|
+
self._load_explicit_settings(mod)
|
68
|
+
# Check for any required settings that are missing
|
69
|
+
self._check_required_settings()
|
70
|
+
# Check for any collected errors
|
71
|
+
self._raise_errors_if_any()
|
72
|
+
|
73
|
+
def _load_module_settings(self, module):
|
74
|
+
annotations = getattr(module, "__annotations__", {})
|
75
|
+
settings = dir(module)
|
76
|
+
|
77
|
+
for setting in settings:
|
78
|
+
if setting.isupper():
|
79
|
+
if setting in self._settings:
|
80
|
+
self._errors.append(f"Duplicate setting '{setting}'.")
|
81
|
+
continue
|
82
|
+
|
83
|
+
setting_value = getattr(module, setting)
|
84
|
+
self._settings[setting] = SettingDefinition(
|
85
|
+
name=setting,
|
86
|
+
default_value=setting_value,
|
87
|
+
annotation=annotations.get(setting, None),
|
88
|
+
module=module,
|
89
|
+
)
|
90
|
+
|
91
|
+
# Store any annotations that didn't have a value (these are required settings)
|
92
|
+
for setting, annotation in annotations.items():
|
93
|
+
if setting not in self._settings:
|
94
|
+
self._settings[setting] = SettingDefinition(
|
95
|
+
name=setting,
|
96
|
+
default_value=None,
|
97
|
+
annotation=annotation,
|
98
|
+
module=module,
|
99
|
+
required=True,
|
100
|
+
)
|
101
|
+
|
102
|
+
def _load_default_settings(self, settings_module):
|
103
|
+
for entry in getattr(settings_module, "INSTALLED_PACKAGES", []):
|
104
|
+
try:
|
105
|
+
if isinstance(entry, PackageConfig):
|
106
|
+
app_settings = entry.module.default_settings
|
107
|
+
else:
|
108
|
+
app_settings = importlib.import_module(f"{entry}.default_settings")
|
109
|
+
except ModuleNotFoundError:
|
110
|
+
continue
|
111
|
+
|
112
|
+
self._load_module_settings(app_settings)
|
113
|
+
|
114
|
+
def _load_env_settings(self):
|
115
|
+
env_settings = {
|
116
|
+
k[len(ENV_SETTINGS_PREFIX) :]: v
|
117
|
+
for k, v in os.environ.items()
|
118
|
+
if k.startswith(ENV_SETTINGS_PREFIX) and k.isupper()
|
119
|
+
}
|
120
|
+
for setting, value in env_settings.items():
|
121
|
+
if setting in self._settings:
|
122
|
+
setting_def = self._settings[setting]
|
123
|
+
try:
|
124
|
+
parsed_value = _parse_env_value(value, setting_def.annotation)
|
125
|
+
setting_def.set_value(parsed_value, "env")
|
126
|
+
except ImproperlyConfigured as e:
|
127
|
+
self._errors.append(str(e))
|
128
|
+
|
129
|
+
def _load_explicit_settings(self, settings_module):
|
130
|
+
for setting in dir(settings_module):
|
131
|
+
if setting.isupper():
|
132
|
+
setting_value = getattr(settings_module, setting)
|
133
|
+
|
134
|
+
if setting in self._settings:
|
135
|
+
setting_def = self._settings[setting]
|
136
|
+
try:
|
137
|
+
setting_def.set_value(setting_value, "explicit")
|
138
|
+
except ImproperlyConfigured as e:
|
139
|
+
self._errors.append(str(e))
|
140
|
+
continue
|
141
|
+
|
142
|
+
elif setting.startswith(CUSTOM_SETTINGS_PREFIX):
|
143
|
+
# Accept custom settings prefixed with '{CUSTOM_SETTINGS_PREFIX}'
|
144
|
+
setting_def = SettingDefinition(
|
145
|
+
name=setting,
|
146
|
+
default_value=None,
|
147
|
+
annotation=None,
|
148
|
+
required=False,
|
149
|
+
)
|
150
|
+
try:
|
151
|
+
setting_def.set_value(setting_value, "explicit")
|
152
|
+
except ImproperlyConfigured as e:
|
153
|
+
self._errors.append(str(e))
|
154
|
+
continue
|
155
|
+
self._settings[setting] = setting_def
|
156
|
+
else:
|
157
|
+
# Collect unrecognized settings individually
|
158
|
+
self._errors.append(
|
159
|
+
f"Unknown setting '{setting}'. Custom settings must start with '{CUSTOM_SETTINGS_PREFIX}'."
|
160
|
+
)
|
161
|
+
|
162
|
+
if hasattr(time, "tzset") and self.TIME_ZONE:
|
163
|
+
zoneinfo_root = Path("/usr/share/zoneinfo")
|
164
|
+
zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
|
165
|
+
if zoneinfo_root.exists() and not zone_info_file.exists():
|
166
|
+
self._errors.append(
|
167
|
+
f"Invalid TIME_ZONE setting '{self.TIME_ZONE}'. Timezone file not found."
|
168
|
+
)
|
169
|
+
else:
|
170
|
+
os.environ["TZ"] = self.TIME_ZONE
|
171
|
+
time.tzset()
|
172
|
+
|
173
|
+
def _check_required_settings(self):
|
174
|
+
missing = [k for k, v in self._settings.items() if v.required and not v.is_set]
|
175
|
+
if missing:
|
176
|
+
self._errors.append(f"Missing required setting(s): {', '.join(missing)}.")
|
177
|
+
|
178
|
+
def _raise_errors_if_any(self):
|
179
|
+
if self._errors:
|
180
|
+
errors = ["- " + e for e in self._errors]
|
181
|
+
raise ImproperlyConfigured(
|
182
|
+
"Settings configuration errors:\n" + "\n".join(errors)
|
183
|
+
)
|
61
184
|
|
62
185
|
def __getattr__(self, name):
|
63
|
-
|
64
|
-
if
|
65
|
-
|
66
|
-
_wrapped = self._wrapped
|
67
|
-
val = getattr(_wrapped, name)
|
186
|
+
# Avoid recursion by directly returning internal attributes
|
187
|
+
if not name.isupper():
|
188
|
+
return object.__getattribute__(self, name)
|
68
189
|
|
69
|
-
|
70
|
-
# This is done here for performance reasons so the modified value is cached.
|
71
|
-
if name == "SECRET_KEY" and not val:
|
72
|
-
raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
|
190
|
+
self._setup()
|
73
191
|
|
74
|
-
|
75
|
-
|
192
|
+
if name in self._settings:
|
193
|
+
return self._settings[name].value
|
194
|
+
else:
|
195
|
+
raise AttributeError(f"'Settings' object has no attribute '{name}'")
|
76
196
|
|
77
197
|
def __setattr__(self, name, value):
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
"""
|
82
|
-
if name == "_wrapped":
|
83
|
-
self.__dict__.clear()
|
198
|
+
# Handle internal attributes without recursion
|
199
|
+
if not name.isupper():
|
200
|
+
object.__setattr__(self, name, value)
|
84
201
|
else:
|
85
|
-
self.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
super().__delattr__(name)
|
91
|
-
self.__dict__.pop(name, None)
|
202
|
+
if name in self._settings:
|
203
|
+
self._settings[name].set_value(value, "runtime")
|
204
|
+
self._raise_errors_if_any()
|
205
|
+
else:
|
206
|
+
object.__setattr__(self, name, value)
|
92
207
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
return self.
|
208
|
+
def __repr__(self):
|
209
|
+
if not self.configured:
|
210
|
+
return "<Settings [Unevaluated]>"
|
211
|
+
return f'<Settings "{self._settings_module}">'
|
212
|
+
|
213
|
+
|
214
|
+
def _parse_env_value(value, annotation):
|
215
|
+
if not annotation:
|
216
|
+
raise ImproperlyConfigured("Type hint required to set from environment.")
|
217
|
+
|
218
|
+
if annotation is bool:
|
219
|
+
# Special case for bools
|
220
|
+
return value.lower() in ("true", "1", "yes")
|
221
|
+
elif annotation is str:
|
222
|
+
return value
|
223
|
+
else:
|
224
|
+
# Parse other types using JSON
|
225
|
+
try:
|
226
|
+
return json.loads(value)
|
227
|
+
except json.JSONDecodeError as e:
|
228
|
+
raise ImproperlyConfigured(
|
229
|
+
f"Invalid JSON value for setting: {e.msg}"
|
230
|
+
) from e
|
97
231
|
|
98
232
|
|
99
233
|
class SettingDefinition:
|
100
|
-
"""Store
|
234
|
+
"""Store detailed information about settings."""
|
101
235
|
|
102
|
-
def __init__(
|
236
|
+
def __init__(
|
237
|
+
self, name, default_value=None, annotation=None, module=None, required=False
|
238
|
+
):
|
103
239
|
self.name = name
|
104
|
-
self.
|
240
|
+
self.default_value = default_value
|
105
241
|
self.annotation = annotation
|
106
242
|
self.module = module
|
107
243
|
self.required = required
|
244
|
+
self.value = default_value
|
245
|
+
self.source = "default" # 'default', 'env', 'explicit', or 'runtime'
|
246
|
+
self.is_set = False # Indicates if the value was set explicitly
|
108
247
|
|
109
|
-
def
|
110
|
-
|
248
|
+
def set_value(self, value, source):
|
249
|
+
self.check_type(value)
|
250
|
+
self.value = value
|
251
|
+
self.source = source
|
252
|
+
self.is_set = True
|
111
253
|
|
112
254
|
def check_type(self, obj):
|
113
255
|
if not self.annotation:
|
114
256
|
return
|
115
257
|
|
116
258
|
if not SettingDefinition._is_instance_of_type(obj, self.annotation):
|
117
|
-
raise
|
118
|
-
f"
|
259
|
+
raise ImproperlyConfigured(
|
260
|
+
f"'{self.name}': Expected type {self.annotation}, but got {type(obj)}."
|
119
261
|
)
|
120
262
|
|
121
263
|
@staticmethod
|
@@ -152,153 +294,20 @@ class SettingDefinition:
|
|
152
294
|
for i, item in enumerate(value)
|
153
295
|
)
|
154
296
|
|
155
|
-
raise ValueError("Unsupported type hint:
|
156
|
-
|
157
|
-
|
158
|
-
class Settings:
|
159
|
-
def __init__(self, settings_module):
|
160
|
-
self._default_settings = {}
|
161
|
-
self._explicit_settings = set()
|
162
|
-
|
163
|
-
# First load the global settings from plain
|
164
|
-
self._load_module_settings(
|
165
|
-
importlib.import_module("plain.runtime.global_settings")
|
166
|
-
)
|
167
|
-
|
168
|
-
# store the settings module in case someone later cares
|
169
|
-
self.SETTINGS_MODULE = settings_module
|
170
|
-
|
171
|
-
mod = importlib.import_module(self.SETTINGS_MODULE)
|
172
|
-
|
173
|
-
# Keep a reference to the settings.py module path
|
174
|
-
# so we can find files next to it (assume it's at the app root)
|
175
|
-
self.path = Path(mod.__file__).resolve()
|
176
|
-
|
177
|
-
# First, get all the default_settings from the INSTALLED_PACKAGES and set those values
|
178
|
-
self._load_default_settings(mod)
|
179
|
-
# Second, look at the environment variables and overwrite with those
|
180
|
-
self._load_env_settings()
|
181
|
-
# Finally, load the explicit settings from the settings module
|
182
|
-
self._load_explicit_settings(mod)
|
183
|
-
# Check for any required settings that are missing
|
184
|
-
self._check_required_settings()
|
185
|
-
|
186
|
-
def _load_default_settings(self, settings_module):
|
187
|
-
# Get INSTALLED_PACKAGES from mod,
|
188
|
-
# then (without populating packages) do a check for default_settings in each
|
189
|
-
# app and load those now too.
|
190
|
-
for entry in getattr(settings_module, "INSTALLED_PACKAGES", []):
|
191
|
-
try:
|
192
|
-
if isinstance(entry, PackageConfig):
|
193
|
-
app_settings = entry.module.default_settings
|
194
|
-
else:
|
195
|
-
app_settings = importlib.import_module(f"{entry}.default_settings")
|
196
|
-
except ModuleNotFoundError:
|
197
|
-
continue
|
198
|
-
|
199
|
-
self._load_module_settings(app_settings)
|
200
|
-
|
201
|
-
def _load_module_settings(self, module):
|
202
|
-
annotations = getattr(module, "__annotations__", {})
|
203
|
-
settings = dir(module)
|
204
|
-
|
205
|
-
for setting in settings:
|
206
|
-
if setting.isupper():
|
207
|
-
if hasattr(self, setting):
|
208
|
-
raise ImproperlyConfigured("The %s setting is duplicated" % setting)
|
297
|
+
raise ValueError(f"Unsupported type hint: {type_hint}")
|
209
298
|
|
210
|
-
|
211
|
-
|
212
|
-
# Set a simple attr on the settings object
|
213
|
-
setattr(self, setting, setting_value)
|
214
|
-
|
215
|
-
# Store a more complex setting reference for more detail
|
216
|
-
self._default_settings[setting] = SettingDefinition(
|
217
|
-
name=setting,
|
218
|
-
value=setting_value,
|
219
|
-
annotation=annotations.get(setting, ""),
|
220
|
-
module=module,
|
221
|
-
)
|
222
|
-
|
223
|
-
# Store any annotations that didn't have a value (these are required settings)
|
224
|
-
for setting, annotation in annotations.items():
|
225
|
-
if setting not in self._default_settings:
|
226
|
-
self._default_settings[setting] = SettingDefinition(
|
227
|
-
name=setting,
|
228
|
-
value=None,
|
229
|
-
annotation=annotation,
|
230
|
-
module=module,
|
231
|
-
required=True,
|
232
|
-
)
|
233
|
-
|
234
|
-
def _load_env_settings(self):
|
235
|
-
env_settings = {
|
236
|
-
k[len(ENV_SETTINGS_PREFIX) :]: v
|
237
|
-
for k, v in os.environ.items()
|
238
|
-
if k.startswith(ENV_SETTINGS_PREFIX)
|
239
|
-
}
|
240
|
-
logger.debug("Loading environment settings: %s", env_settings)
|
241
|
-
for setting, value in env_settings.items():
|
242
|
-
if setting not in self._default_settings:
|
243
|
-
# Ignore anything not defined in the default settings
|
244
|
-
continue
|
245
|
-
|
246
|
-
default_setting = self._default_settings[setting]
|
247
|
-
if not default_setting.annotation:
|
248
|
-
raise ValueError(
|
249
|
-
f"Setting {setting} needs a type hint to be set from the environment"
|
250
|
-
)
|
251
|
-
|
252
|
-
if default_setting.annotation is bool:
|
253
|
-
# Special case for bools
|
254
|
-
parsed_value = value.lower() in ("true", "1", "yes")
|
255
|
-
elif default_setting.annotation is str:
|
256
|
-
parsed_value = value
|
257
|
-
else:
|
258
|
-
# Anything besides a string will be parsed as JSON
|
259
|
-
# (works for ints, lists, etc.)
|
260
|
-
parsed_value = json.loads(value)
|
261
|
-
|
262
|
-
default_setting.check_type(parsed_value)
|
263
|
-
|
264
|
-
setattr(self, setting, parsed_value)
|
265
|
-
self._explicit_settings.add(setting)
|
266
|
-
|
267
|
-
def _load_explicit_settings(self, settings_module):
|
268
|
-
for setting in dir(settings_module):
|
269
|
-
if setting.isupper():
|
270
|
-
setting_value = getattr(settings_module, setting)
|
271
|
-
|
272
|
-
if setting in self._default_settings:
|
273
|
-
self._default_settings[setting].check_type(setting_value)
|
274
|
-
|
275
|
-
setattr(self, setting, setting_value)
|
276
|
-
self._explicit_settings.add(setting)
|
299
|
+
def __str__(self):
|
300
|
+
return f"SettingDefinition(name={self.name}, value={self.value}, source={self.source})"
|
277
301
|
|
278
|
-
if hasattr(time, "tzset") and self.TIME_ZONE:
|
279
|
-
# When we can, attempt to validate the timezone. If we can't find
|
280
|
-
# this file, no check happens and it's harmless.
|
281
|
-
zoneinfo_root = Path("/usr/share/zoneinfo")
|
282
|
-
zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
|
283
|
-
if zoneinfo_root.exists() and not zone_info_file.exists():
|
284
|
-
raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
|
285
|
-
# Move the time zone info into os.environ. See ticket #2315 for why
|
286
|
-
# we don't do this unconditionally (breaks Windows).
|
287
|
-
os.environ["TZ"] = self.TIME_ZONE
|
288
|
-
time.tzset()
|
289
302
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
raise ImproperlyConfigured(
|
296
|
-
"The following settings are required: %s" % ", ".join(missing)
|
297
|
-
)
|
303
|
+
class SettingsReference(str):
|
304
|
+
"""
|
305
|
+
String subclass which references a current settings value. It's treated as
|
306
|
+
the value in memory but serializes to a settings.NAME attribute reference.
|
307
|
+
"""
|
298
308
|
|
299
|
-
|
300
|
-
|
301
|
-
return setting in self._explicit_settings
|
309
|
+
def __new__(self, value, setting_name):
|
310
|
+
return str.__new__(self, value)
|
302
311
|
|
303
|
-
def
|
304
|
-
|
312
|
+
def __init__(self, value, setting_name):
|
313
|
+
self.setting_name = setting_name
|
plain/signing.py
CHANGED
@@ -37,12 +37,10 @@ import base64
|
|
37
37
|
import datetime
|
38
38
|
import json
|
39
39
|
import time
|
40
|
-
import warnings
|
41
40
|
import zlib
|
42
41
|
|
43
42
|
from plain.runtime import settings
|
44
43
|
from plain.utils.crypto import constant_time_compare, salted_hmac
|
45
|
-
from plain.utils.deprecation import RemovedInDjango51Warning
|
46
44
|
from plain.utils.encoding import force_bytes
|
47
45
|
from plain.utils.module_loading import import_string
|
48
46
|
from plain.utils.regex_helper import _lazy_re_compile
|
@@ -109,7 +107,7 @@ def _cookie_signer_key(key):
|
|
109
107
|
|
110
108
|
|
111
109
|
def get_cookie_signer(salt="plain.signing.get_cookie_signer"):
|
112
|
-
Signer = import_string(settings.
|
110
|
+
Signer = import_string(settings.COOKIE_SIGNING_BACKEND)
|
113
111
|
return Signer(
|
114
112
|
key=_cookie_signer_key(settings.SECRET_KEY),
|
115
113
|
fallback_keys=map(_cookie_signer_key, settings.SECRET_KEY_FALLBACKS),
|
@@ -177,17 +175,13 @@ def loads(
|
|
177
175
|
|
178
176
|
|
179
177
|
class Signer:
|
180
|
-
# RemovedInDjango51Warning: When the deprecation ends, replace with:
|
181
|
-
# def __init__(
|
182
|
-
# self, *, key=None, sep=":", salt=None, algorithm=None, fallback_keys=None
|
183
|
-
# ):
|
184
178
|
def __init__(
|
185
179
|
self,
|
186
|
-
|
180
|
+
*,
|
187
181
|
key=None,
|
188
182
|
sep=":",
|
189
183
|
salt=None,
|
190
|
-
algorithm=
|
184
|
+
algorithm="sha256",
|
191
185
|
fallback_keys=None,
|
192
186
|
):
|
193
187
|
self.key = key or settings.SECRET_KEY
|
@@ -198,20 +192,8 @@ class Signer:
|
|
198
192
|
)
|
199
193
|
self.sep = sep
|
200
194
|
self.salt = salt or f"{self.__class__.__module__}.{self.__class__.__name__}"
|
201
|
-
self.algorithm = algorithm
|
202
|
-
|
203
|
-
if args:
|
204
|
-
warnings.warn(
|
205
|
-
f"Passing positional arguments to {self.__class__.__name__} is "
|
206
|
-
f"deprecated.",
|
207
|
-
RemovedInDjango51Warning,
|
208
|
-
stacklevel=2,
|
209
|
-
)
|
210
|
-
for arg, attr in zip(
|
211
|
-
args, ["key", "sep", "salt", "algorithm", "fallback_keys"]
|
212
|
-
):
|
213
|
-
if arg or attr == "sep":
|
214
|
-
setattr(self, attr, arg)
|
195
|
+
self.algorithm = algorithm
|
196
|
+
|
215
197
|
if _SEP_UNSAFE.match(self.sep):
|
216
198
|
raise ValueError(
|
217
199
|
"Unsafe Signer separator: %r (cannot be empty or consist of "
|