plain 0.5.0__tar.gz → 0.7.0__tar.gz
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-0.5.0 → plain-0.7.0}/PKG-INFO +1 -1
- {plain-0.5.0 → plain-0.7.0}/plain/cli/cli.py +35 -12
- {plain-0.5.0 → plain-0.7.0}/plain/forms/fields.py +2 -3
- plain-0.7.0/plain/middleware/security.py +31 -0
- plain-0.7.0/plain/preflight/security/base.py +114 -0
- {plain-0.5.0 → plain-0.7.0}/plain/runtime/README.md +0 -1
- {plain-0.5.0 → plain-0.7.0}/plain/runtime/__init__.py +2 -2
- {plain-0.5.0 → plain-0.7.0}/plain/runtime/global_settings.py +16 -29
- plain-0.7.0/plain/runtime/user_settings.py +313 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signing.py +1 -1
- {plain-0.5.0 → plain-0.7.0}/plain/utils/timezone.py +2 -23
- {plain-0.5.0 → plain-0.7.0}/pyproject.toml +1 -1
- plain-0.5.0/plain/middleware/clickjacking.py +0 -52
- plain-0.5.0/plain/middleware/security.py +0 -64
- plain-0.5.0/plain/preflight/security/base.py +0 -268
- plain-0.5.0/plain/runtime/user_settings.py +0 -304
- {plain-0.5.0 → plain-0.7.0}/LICENSE +0 -0
- {plain-0.5.0 → plain-0.7.0}/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/__main__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/compile.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/finders.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/fingerprints.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/urls.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/assets/views.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/formatting.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/packages.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/print.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/cli/startup.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/csrf/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/csrf/middleware.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/csrf/views.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/debug.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/exceptions.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/forms/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/forms/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/forms/boundfield.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/forms/exceptions.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/forms/forms.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/cookie.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/multipartparser.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/request.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/http/response.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/base.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/locks.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/move.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/temp.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/uploadedfile.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/uploadhandler.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/files/utils.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/base.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/exception.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/wsgi.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/json.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/logs/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/logs/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/logs/configure.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/logs/loggers.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/logs/utils.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/middleware/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/middleware/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/middleware/common.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/middleware/gzip.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/packages/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/packages/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/packages/config.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/packages/registry.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/paginator.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/files.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/messages.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/registry.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/security/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/security/csrf.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/preflight/urls.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signals/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signals/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/dispatcher.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/license.txt +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/core.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/defaults.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/extensions.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/filters.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/globals.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/test/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/test/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/test/client.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/base.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/conf.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/converters.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/exceptions.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/urls/resolvers.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/_os.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/cache.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/connection.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/crypto.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/datastructures.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/dateformat.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/dateparse.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/dates.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/deconstruct.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/decorators.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/deprecation.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/duration.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/email.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/encoding.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/functional.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/hashable.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/html.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/http.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/inspect.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/ipv6.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/itercompat.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/module_loading.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/regex_helper.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/safestring.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/termcolors.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/text.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/timesince.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/utils/tree.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/validators.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/README.md +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/__init__.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/base.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/csrf.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/errors.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/exceptions.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/forms.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/objects.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/redirect.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/views/templates.py +0 -0
- {plain-0.5.0 → plain-0.7.0}/plain/wsgi.py +0 -0
@@ -5,6 +5,7 @@ import shutil
|
|
5
5
|
import subprocess
|
6
6
|
import sys
|
7
7
|
import traceback
|
8
|
+
from importlib.metadata import entry_points
|
8
9
|
from importlib.util import find_spec
|
9
10
|
from pathlib import Path
|
10
11
|
|
@@ -14,7 +15,9 @@ from click.core import Command, Context
|
|
14
15
|
import plain.runtime
|
15
16
|
from plain import preflight
|
16
17
|
from plain.assets.compile import compile_assets, get_compiled_path
|
18
|
+
from plain.exceptions import ImproperlyConfigured
|
17
19
|
from plain.packages import packages
|
20
|
+
from plain.utils.crypto import get_random_string
|
18
21
|
|
19
22
|
from .formatting import PlainContext
|
20
23
|
from .packages import EntryPointGroup, InstalledPackagesGroup
|
@@ -284,17 +287,10 @@ def compile(keep_original, fingerprint, compress):
|
|
284
287
|
)
|
285
288
|
sys.exit(1)
|
286
289
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
click.secho("Compiling Tailwind CSS", bold=True)
|
291
|
-
result = subprocess.run(["plain", "tailwind", "compile", "--minify"])
|
290
|
+
for entry_point in entry_points(group="plain.assets.compile"):
|
291
|
+
click.secho(f"Running {entry_point.name}", bold=True)
|
292
|
+
result = entry_point.load()()
|
292
293
|
print()
|
293
|
-
if result.returncode:
|
294
|
-
click.secho(
|
295
|
-
f"Error compiling Tailwind CSS (exit {result.returncode})", fg="red"
|
296
|
-
)
|
297
|
-
sys.exit(result.returncode)
|
298
294
|
|
299
295
|
# TODO also look in [tool.plain.compile.run]
|
300
296
|
|
@@ -406,6 +402,18 @@ def setting(setting_name):
|
|
406
402
|
click.secho(f'Setting "{setting_name}" not found', fg="red")
|
407
403
|
|
408
404
|
|
405
|
+
@plain_cli.group()
|
406
|
+
def utils():
|
407
|
+
pass
|
408
|
+
|
409
|
+
|
410
|
+
@utils.command()
|
411
|
+
def generate_secret_key():
|
412
|
+
"""Generate a new secret key"""
|
413
|
+
new_secret_key = get_random_string(50)
|
414
|
+
click.echo(new_secret_key)
|
415
|
+
|
416
|
+
|
409
417
|
class AppCLIGroup(click.Group):
|
410
418
|
"""
|
411
419
|
Loads app.cli if it exists as `plain app`
|
@@ -459,16 +467,31 @@ class PlainCommandCollection(click.CommandCollection):
|
|
459
467
|
EntryPointGroup(),
|
460
468
|
plain_cli,
|
461
469
|
]
|
462
|
-
except
|
470
|
+
except ImproperlyConfigured as e:
|
463
471
|
click.secho(
|
464
|
-
|
472
|
+
str(e),
|
465
473
|
fg="red",
|
466
474
|
err=True,
|
467
475
|
)
|
476
|
+
|
477
|
+
# None of these require the app to be setup
|
478
|
+
sources = [
|
479
|
+
EntryPointGroup(),
|
480
|
+
AppCLIGroup(),
|
481
|
+
plain_cli,
|
482
|
+
]
|
483
|
+
except Exception as e:
|
468
484
|
print("---")
|
469
485
|
print(traceback.format_exc())
|
470
486
|
print("---")
|
471
487
|
|
488
|
+
click.secho(
|
489
|
+
f"Error: {e}",
|
490
|
+
fg="red",
|
491
|
+
err=True,
|
492
|
+
)
|
493
|
+
|
494
|
+
# None of these require the app to be setup
|
472
495
|
sources = [
|
473
496
|
EntryPointGroup(),
|
474
497
|
AppCLIGroup(),
|
@@ -14,7 +14,6 @@ from urllib.parse import urlsplit, urlunsplit
|
|
14
14
|
|
15
15
|
from plain import validators
|
16
16
|
from plain.exceptions import ValidationError
|
17
|
-
from plain.runtime import settings
|
18
17
|
from plain.utils import timezone
|
19
18
|
from plain.utils.dateparse import parse_datetime, parse_duration
|
20
19
|
from plain.utils.duration import duration_string
|
@@ -1001,7 +1000,7 @@ def from_current_timezone(value):
|
|
1001
1000
|
When time zone support is enabled, convert naive datetimes
|
1002
1001
|
entered in the current time zone to aware datetimes.
|
1003
1002
|
"""
|
1004
|
-
if
|
1003
|
+
if value is not None and timezone.is_naive(value):
|
1005
1004
|
current_timezone = timezone.get_current_timezone()
|
1006
1005
|
try:
|
1007
1006
|
if timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
|
@@ -1025,6 +1024,6 @@ def to_current_timezone(value):
|
|
1025
1024
|
When time zone support is enabled, convert aware datetimes
|
1026
1025
|
to naive datetimes in the current time zone for display.
|
1027
1026
|
"""
|
1028
|
-
if
|
1027
|
+
if value is not None and timezone.is_aware(value):
|
1029
1028
|
return timezone.make_naive(value)
|
1030
1029
|
return value
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from plain.http import ResponsePermanentRedirect
|
4
|
+
from plain.runtime import settings
|
5
|
+
|
6
|
+
|
7
|
+
class SecurityMiddleware:
|
8
|
+
def __init__(self, get_response):
|
9
|
+
self.get_response = get_response
|
10
|
+
self.redirect = settings.SECURE_SSL_REDIRECT
|
11
|
+
self.redirect_host = settings.SECURE_SSL_HOST
|
12
|
+
self.redirect_exempt = [re.compile(r) for r in settings.SECURE_REDIRECT_EXEMPT]
|
13
|
+
|
14
|
+
self.default_headers = settings.SECURE_DEFAULT_HEADERS
|
15
|
+
|
16
|
+
def __call__(self, request):
|
17
|
+
path = request.path.lstrip("/")
|
18
|
+
if (
|
19
|
+
self.redirect
|
20
|
+
and not request.is_secure()
|
21
|
+
and not any(pattern.search(path) for pattern in self.redirect_exempt)
|
22
|
+
):
|
23
|
+
host = self.redirect_host or request.get_host()
|
24
|
+
return ResponsePermanentRedirect(f"https://{host}{request.get_full_path()}")
|
25
|
+
|
26
|
+
response = self.get_response(request)
|
27
|
+
|
28
|
+
for header, value in self.default_headers.items():
|
29
|
+
response.headers.setdefault(header, value)
|
30
|
+
|
31
|
+
return response
|
@@ -0,0 +1,114 @@
|
|
1
|
+
from plain.exceptions import ImproperlyConfigured
|
2
|
+
from plain.runtime import settings
|
3
|
+
|
4
|
+
from .. import Warning, register
|
5
|
+
|
6
|
+
SECRET_KEY_INSECURE_PREFIX = "plain-insecure-"
|
7
|
+
SECRET_KEY_MIN_LENGTH = 50
|
8
|
+
SECRET_KEY_MIN_UNIQUE_CHARACTERS = 5
|
9
|
+
|
10
|
+
SECRET_KEY_WARNING_MSG = (
|
11
|
+
f"Your %s has less than {SECRET_KEY_MIN_LENGTH} characters, less than "
|
12
|
+
f"{SECRET_KEY_MIN_UNIQUE_CHARACTERS} unique characters, or it's prefixed "
|
13
|
+
f"with '{SECRET_KEY_INSECURE_PREFIX}' indicating that it was generated "
|
14
|
+
f"automatically by Plain. Please generate a long and random value, "
|
15
|
+
f"otherwise many of Plain's security-critical features will be "
|
16
|
+
f"vulnerable to attack."
|
17
|
+
)
|
18
|
+
|
19
|
+
W001 = Warning(
|
20
|
+
"You do not have 'plain.middleware.security.SecurityMiddleware' "
|
21
|
+
"in your MIDDLEWARE so the SECURE_HSTS_SECONDS, "
|
22
|
+
"SECURE_CONTENT_TYPE_NOSNIFF, SECURE_REFERRER_POLICY, "
|
23
|
+
"SECURE_CROSS_ORIGIN_OPENER_POLICY, and SECURE_SSL_REDIRECT settings will "
|
24
|
+
"have no effect.",
|
25
|
+
id="security.W001",
|
26
|
+
)
|
27
|
+
|
28
|
+
W008 = Warning(
|
29
|
+
"Your SECURE_SSL_REDIRECT setting is not set to True. "
|
30
|
+
"Unless your site should be available over both SSL and non-SSL "
|
31
|
+
"connections, you may want to either set this setting True "
|
32
|
+
"or configure a load balancer or reverse-proxy server "
|
33
|
+
"to redirect all connections to HTTPS.",
|
34
|
+
id="security.W008",
|
35
|
+
)
|
36
|
+
|
37
|
+
W009 = Warning(
|
38
|
+
SECRET_KEY_WARNING_MSG % "SECRET_KEY",
|
39
|
+
id="security.W009",
|
40
|
+
)
|
41
|
+
|
42
|
+
W018 = Warning(
|
43
|
+
"You should not have DEBUG set to True in deployment.",
|
44
|
+
id="security.W018",
|
45
|
+
)
|
46
|
+
|
47
|
+
W020 = Warning(
|
48
|
+
"ALLOWED_HOSTS must not be empty in deployment.",
|
49
|
+
id="security.W020",
|
50
|
+
)
|
51
|
+
|
52
|
+
W025 = Warning(SECRET_KEY_WARNING_MSG, id="security.W025")
|
53
|
+
|
54
|
+
|
55
|
+
def _security_middleware():
|
56
|
+
return "plain.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE
|
57
|
+
|
58
|
+
|
59
|
+
@register(deploy=True)
|
60
|
+
def check_security_middleware(package_configs, **kwargs):
|
61
|
+
passed_check = _security_middleware()
|
62
|
+
return [] if passed_check else [W001]
|
63
|
+
|
64
|
+
|
65
|
+
@register(deploy=True)
|
66
|
+
def check_ssl_redirect(package_configs, **kwargs):
|
67
|
+
passed_check = not _security_middleware() or settings.SECURE_SSL_REDIRECT is True
|
68
|
+
return [] if passed_check else [W008]
|
69
|
+
|
70
|
+
|
71
|
+
def _check_secret_key(secret_key):
|
72
|
+
return (
|
73
|
+
len(set(secret_key)) >= SECRET_KEY_MIN_UNIQUE_CHARACTERS
|
74
|
+
and len(secret_key) >= SECRET_KEY_MIN_LENGTH
|
75
|
+
and not secret_key.startswith(SECRET_KEY_INSECURE_PREFIX)
|
76
|
+
)
|
77
|
+
|
78
|
+
|
79
|
+
@register(deploy=True)
|
80
|
+
def check_secret_key(package_configs, **kwargs):
|
81
|
+
try:
|
82
|
+
secret_key = settings.SECRET_KEY
|
83
|
+
except (ImproperlyConfigured, AttributeError):
|
84
|
+
passed_check = False
|
85
|
+
else:
|
86
|
+
passed_check = _check_secret_key(secret_key)
|
87
|
+
return [] if passed_check else [W009]
|
88
|
+
|
89
|
+
|
90
|
+
@register(deploy=True)
|
91
|
+
def check_secret_key_fallbacks(package_configs, **kwargs):
|
92
|
+
warnings = []
|
93
|
+
try:
|
94
|
+
fallbacks = settings.SECRET_KEY_FALLBACKS
|
95
|
+
except (ImproperlyConfigured, AttributeError):
|
96
|
+
warnings.append(Warning(W025.msg % "SECRET_KEY_FALLBACKS", id=W025.id))
|
97
|
+
else:
|
98
|
+
for index, key in enumerate(fallbacks):
|
99
|
+
if not _check_secret_key(key):
|
100
|
+
warnings.append(
|
101
|
+
Warning(W025.msg % f"SECRET_KEY_FALLBACKS[{index}]", id=W025.id)
|
102
|
+
)
|
103
|
+
return warnings
|
104
|
+
|
105
|
+
|
106
|
+
@register(deploy=True)
|
107
|
+
def check_debug(package_configs, **kwargs):
|
108
|
+
passed_check = not settings.DEBUG
|
109
|
+
return [] if passed_check else [W018]
|
110
|
+
|
111
|
+
|
112
|
+
@register(deploy=True)
|
113
|
+
def check_allowed_hosts(package_configs, **kwargs):
|
114
|
+
return [] if settings.ALLOWED_HOSTS else [W020]
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
5
5
|
|
6
6
|
from dotenv import load_dotenv
|
7
7
|
|
8
|
-
from .user_settings import
|
8
|
+
from .user_settings import Settings
|
9
9
|
|
10
10
|
try:
|
11
11
|
__version__ = importlib.metadata.version("plain")
|
@@ -18,7 +18,7 @@ APP_PATH = Path.cwd() / "app"
|
|
18
18
|
|
19
19
|
|
20
20
|
# from plain.runtime import settings
|
21
|
-
settings =
|
21
|
+
settings = Settings()
|
22
22
|
|
23
23
|
|
24
24
|
class AppPathNotFound(RuntimeError):
|
@@ -20,12 +20,9 @@ ALLOWED_HOSTS: list[str] = []
|
|
20
20
|
|
21
21
|
# Local time zone for this installation. All choices can be found here:
|
22
22
|
# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
|
23
|
-
# systems may support all possibilities).
|
24
|
-
#
|
25
|
-
TIME_ZONE = "
|
26
|
-
|
27
|
-
# If you set this to True, Plain will use timezone-aware datetimes.
|
28
|
-
USE_TZ = True
|
23
|
+
# systems may support all possibilities). This is interpreted as the default
|
24
|
+
# user time zone.
|
25
|
+
TIME_ZONE: str = "UTC"
|
29
26
|
|
30
27
|
# Default charset to use for all Response objects, if a MIME type isn't
|
31
28
|
# manually specified. It's used to construct the Content-Type header.
|
@@ -75,19 +72,6 @@ DATA_UPLOAD_MAX_NUMBER_FILES = 100
|
|
75
72
|
# (i.e. "/tmp" on *nix systems).
|
76
73
|
FILE_UPLOAD_TEMP_DIR = None
|
77
74
|
|
78
|
-
# The numeric mode to set newly-uploaded files to. The value should be a mode
|
79
|
-
# you'd pass directly to os.chmod; see
|
80
|
-
# https://docs.python.org/library/os.html#files-and-directories.
|
81
|
-
FILE_UPLOAD_PERMISSIONS = 0o644
|
82
|
-
|
83
|
-
# The numeric mode to assign to newly-created directories, when uploading files.
|
84
|
-
# The value should be a mode as you'd pass to os.chmod;
|
85
|
-
# see https://docs.python.org/library/os.html#files-and-directories.
|
86
|
-
FILE_UPLOAD_DIRECTORY_PERMISSIONS = None
|
87
|
-
|
88
|
-
# Default X-Frame-Options header value
|
89
|
-
X_FRAME_OPTIONS = "DENY"
|
90
|
-
|
91
75
|
USE_X_FORWARDED_HOST = False
|
92
76
|
USE_X_FORWARDED_PORT = False
|
93
77
|
|
@@ -114,14 +98,13 @@ MIDDLEWARE = [
|
|
114
98
|
"plain.middleware.security.SecurityMiddleware",
|
115
99
|
"plain.middleware.common.CommonMiddleware",
|
116
100
|
"plain.csrf.middleware.CsrfViewMiddleware",
|
117
|
-
"plain.middleware.clickjacking.XFrameOptionsMiddleware",
|
118
101
|
]
|
119
102
|
|
120
103
|
###########
|
121
104
|
# SIGNING #
|
122
105
|
###########
|
123
106
|
|
124
|
-
|
107
|
+
COOKIE_SIGNING_BACKEND = "plain.signing.TimestampSigner"
|
125
108
|
|
126
109
|
########
|
127
110
|
# CSRF #
|
@@ -132,7 +115,7 @@ CSRF_COOKIE_NAME = "csrftoken"
|
|
132
115
|
CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52
|
133
116
|
CSRF_COOKIE_DOMAIN = None
|
134
117
|
CSRF_COOKIE_PATH = "/"
|
135
|
-
CSRF_COOKIE_SECURE =
|
118
|
+
CSRF_COOKIE_SECURE = True
|
136
119
|
CSRF_COOKIE_HTTPONLY = False
|
137
120
|
CSRF_COOKIE_SAMESITE = "Lax"
|
138
121
|
CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN"
|
@@ -170,15 +153,19 @@ SILENCED_PREFLIGHT_CHECKS = []
|
|
170
153
|
#######################
|
171
154
|
# SECURITY MIDDLEWARE #
|
172
155
|
#######################
|
173
|
-
SECURE_CONTENT_TYPE_NOSNIFF = True
|
174
|
-
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
|
175
|
-
SECURE_HSTS_INCLUDE_SUBDOMAINS = False
|
176
|
-
SECURE_HSTS_PRELOAD = False
|
177
|
-
SECURE_HSTS_SECONDS = 0
|
178
156
|
SECURE_REDIRECT_EXEMPT = []
|
179
|
-
SECURE_REFERRER_POLICY = "same-origin"
|
180
157
|
SECURE_SSL_HOST = None
|
181
|
-
SECURE_SSL_REDIRECT =
|
158
|
+
SECURE_SSL_REDIRECT = True
|
159
|
+
|
160
|
+
SECURE_DEFAULT_HEADERS = {
|
161
|
+
# "Content-Security-Policy": "default-src 'self'",
|
162
|
+
# https://hstspreload.org/
|
163
|
+
# "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
|
164
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
165
|
+
"Referrer-Policy": "same-origin",
|
166
|
+
"X-Content-Type-Options": "nosniff",
|
167
|
+
"X-Frame-Options": "DENY",
|
168
|
+
}
|
182
169
|
|
183
170
|
#############
|
184
171
|
# Templates #
|