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.
Files changed (152) hide show
  1. {plain-0.5.0 → plain-0.7.0}/PKG-INFO +1 -1
  2. {plain-0.5.0 → plain-0.7.0}/plain/cli/cli.py +35 -12
  3. {plain-0.5.0 → plain-0.7.0}/plain/forms/fields.py +2 -3
  4. plain-0.7.0/plain/middleware/security.py +31 -0
  5. plain-0.7.0/plain/preflight/security/base.py +114 -0
  6. {plain-0.5.0 → plain-0.7.0}/plain/runtime/README.md +0 -1
  7. {plain-0.5.0 → plain-0.7.0}/plain/runtime/__init__.py +2 -2
  8. {plain-0.5.0 → plain-0.7.0}/plain/runtime/global_settings.py +16 -29
  9. plain-0.7.0/plain/runtime/user_settings.py +313 -0
  10. {plain-0.5.0 → plain-0.7.0}/plain/signing.py +1 -1
  11. {plain-0.5.0 → plain-0.7.0}/plain/utils/timezone.py +2 -23
  12. {plain-0.5.0 → plain-0.7.0}/pyproject.toml +1 -1
  13. plain-0.5.0/plain/middleware/clickjacking.py +0 -52
  14. plain-0.5.0/plain/middleware/security.py +0 -64
  15. plain-0.5.0/plain/preflight/security/base.py +0 -268
  16. plain-0.5.0/plain/runtime/user_settings.py +0 -304
  17. {plain-0.5.0 → plain-0.7.0}/LICENSE +0 -0
  18. {plain-0.5.0 → plain-0.7.0}/README.md +0 -0
  19. {plain-0.5.0 → plain-0.7.0}/plain/README.md +0 -0
  20. {plain-0.5.0 → plain-0.7.0}/plain/__main__.py +0 -0
  21. {plain-0.5.0 → plain-0.7.0}/plain/assets/README.md +0 -0
  22. {plain-0.5.0 → plain-0.7.0}/plain/assets/__init__.py +0 -0
  23. {plain-0.5.0 → plain-0.7.0}/plain/assets/compile.py +0 -0
  24. {plain-0.5.0 → plain-0.7.0}/plain/assets/finders.py +0 -0
  25. {plain-0.5.0 → plain-0.7.0}/plain/assets/fingerprints.py +0 -0
  26. {plain-0.5.0 → plain-0.7.0}/plain/assets/urls.py +0 -0
  27. {plain-0.5.0 → plain-0.7.0}/plain/assets/views.py +0 -0
  28. {plain-0.5.0 → plain-0.7.0}/plain/cli/README.md +0 -0
  29. {plain-0.5.0 → plain-0.7.0}/plain/cli/__init__.py +0 -0
  30. {plain-0.5.0 → plain-0.7.0}/plain/cli/formatting.py +0 -0
  31. {plain-0.5.0 → plain-0.7.0}/plain/cli/packages.py +0 -0
  32. {plain-0.5.0 → plain-0.7.0}/plain/cli/print.py +0 -0
  33. {plain-0.5.0 → plain-0.7.0}/plain/cli/startup.py +0 -0
  34. {plain-0.5.0 → plain-0.7.0}/plain/csrf/README.md +0 -0
  35. {plain-0.5.0 → plain-0.7.0}/plain/csrf/middleware.py +0 -0
  36. {plain-0.5.0 → plain-0.7.0}/plain/csrf/views.py +0 -0
  37. {plain-0.5.0 → plain-0.7.0}/plain/debug.py +0 -0
  38. {plain-0.5.0 → plain-0.7.0}/plain/exceptions.py +0 -0
  39. {plain-0.5.0 → plain-0.7.0}/plain/forms/README.md +0 -0
  40. {plain-0.5.0 → plain-0.7.0}/plain/forms/__init__.py +0 -0
  41. {plain-0.5.0 → plain-0.7.0}/plain/forms/boundfield.py +0 -0
  42. {plain-0.5.0 → plain-0.7.0}/plain/forms/exceptions.py +0 -0
  43. {plain-0.5.0 → plain-0.7.0}/plain/forms/forms.py +0 -0
  44. {plain-0.5.0 → plain-0.7.0}/plain/http/README.md +0 -0
  45. {plain-0.5.0 → plain-0.7.0}/plain/http/__init__.py +0 -0
  46. {plain-0.5.0 → plain-0.7.0}/plain/http/cookie.py +0 -0
  47. {plain-0.5.0 → plain-0.7.0}/plain/http/multipartparser.py +0 -0
  48. {plain-0.5.0 → plain-0.7.0}/plain/http/request.py +0 -0
  49. {plain-0.5.0 → plain-0.7.0}/plain/http/response.py +0 -0
  50. {plain-0.5.0 → plain-0.7.0}/plain/internal/__init__.py +0 -0
  51. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/README.md +0 -0
  52. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/__init__.py +0 -0
  53. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/base.py +0 -0
  54. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/locks.py +0 -0
  55. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/move.py +0 -0
  56. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/temp.py +0 -0
  57. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/uploadedfile.py +0 -0
  58. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/uploadhandler.py +0 -0
  59. {plain-0.5.0 → plain-0.7.0}/plain/internal/files/utils.py +0 -0
  60. {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/__init__.py +0 -0
  61. {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/base.py +0 -0
  62. {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/exception.py +0 -0
  63. {plain-0.5.0 → plain-0.7.0}/plain/internal/handlers/wsgi.py +0 -0
  64. {plain-0.5.0 → plain-0.7.0}/plain/json.py +0 -0
  65. {plain-0.5.0 → plain-0.7.0}/plain/logs/README.md +0 -0
  66. {plain-0.5.0 → plain-0.7.0}/plain/logs/__init__.py +0 -0
  67. {plain-0.5.0 → plain-0.7.0}/plain/logs/configure.py +0 -0
  68. {plain-0.5.0 → plain-0.7.0}/plain/logs/loggers.py +0 -0
  69. {plain-0.5.0 → plain-0.7.0}/plain/logs/utils.py +0 -0
  70. {plain-0.5.0 → plain-0.7.0}/plain/middleware/README.md +0 -0
  71. {plain-0.5.0 → plain-0.7.0}/plain/middleware/__init__.py +0 -0
  72. {plain-0.5.0 → plain-0.7.0}/plain/middleware/common.py +0 -0
  73. {plain-0.5.0 → plain-0.7.0}/plain/middleware/gzip.py +0 -0
  74. {plain-0.5.0 → plain-0.7.0}/plain/packages/README.md +0 -0
  75. {plain-0.5.0 → plain-0.7.0}/plain/packages/__init__.py +0 -0
  76. {plain-0.5.0 → plain-0.7.0}/plain/packages/config.py +0 -0
  77. {plain-0.5.0 → plain-0.7.0}/plain/packages/registry.py +0 -0
  78. {plain-0.5.0 → plain-0.7.0}/plain/paginator.py +0 -0
  79. {plain-0.5.0 → plain-0.7.0}/plain/preflight/README.md +0 -0
  80. {plain-0.5.0 → plain-0.7.0}/plain/preflight/__init__.py +0 -0
  81. {plain-0.5.0 → plain-0.7.0}/plain/preflight/files.py +0 -0
  82. {plain-0.5.0 → plain-0.7.0}/plain/preflight/messages.py +0 -0
  83. {plain-0.5.0 → plain-0.7.0}/plain/preflight/registry.py +0 -0
  84. {plain-0.5.0 → plain-0.7.0}/plain/preflight/security/__init__.py +0 -0
  85. {plain-0.5.0 → plain-0.7.0}/plain/preflight/security/csrf.py +0 -0
  86. {plain-0.5.0 → plain-0.7.0}/plain/preflight/urls.py +0 -0
  87. {plain-0.5.0 → plain-0.7.0}/plain/signals/README.md +0 -0
  88. {plain-0.5.0 → plain-0.7.0}/plain/signals/__init__.py +0 -0
  89. {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/__init__.py +0 -0
  90. {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/dispatcher.py +0 -0
  91. {plain-0.5.0 → plain-0.7.0}/plain/signals/dispatch/license.txt +0 -0
  92. {plain-0.5.0 → plain-0.7.0}/plain/templates/README.md +0 -0
  93. {plain-0.5.0 → plain-0.7.0}/plain/templates/__init__.py +0 -0
  94. {plain-0.5.0 → plain-0.7.0}/plain/templates/core.py +0 -0
  95. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/README.md +0 -0
  96. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/__init__.py +0 -0
  97. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/defaults.py +0 -0
  98. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/extensions.py +0 -0
  99. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/filters.py +0 -0
  100. {plain-0.5.0 → plain-0.7.0}/plain/templates/jinja/globals.py +0 -0
  101. {plain-0.5.0 → plain-0.7.0}/plain/test/README.md +0 -0
  102. {plain-0.5.0 → plain-0.7.0}/plain/test/__init__.py +0 -0
  103. {plain-0.5.0 → plain-0.7.0}/plain/test/client.py +0 -0
  104. {plain-0.5.0 → plain-0.7.0}/plain/urls/README.md +0 -0
  105. {plain-0.5.0 → plain-0.7.0}/plain/urls/__init__.py +0 -0
  106. {plain-0.5.0 → plain-0.7.0}/plain/urls/base.py +0 -0
  107. {plain-0.5.0 → plain-0.7.0}/plain/urls/conf.py +0 -0
  108. {plain-0.5.0 → plain-0.7.0}/plain/urls/converters.py +0 -0
  109. {plain-0.5.0 → plain-0.7.0}/plain/urls/exceptions.py +0 -0
  110. {plain-0.5.0 → plain-0.7.0}/plain/urls/resolvers.py +0 -0
  111. {plain-0.5.0 → plain-0.7.0}/plain/utils/README.md +0 -0
  112. {plain-0.5.0 → plain-0.7.0}/plain/utils/__init__.py +0 -0
  113. {plain-0.5.0 → plain-0.7.0}/plain/utils/_os.py +0 -0
  114. {plain-0.5.0 → plain-0.7.0}/plain/utils/cache.py +0 -0
  115. {plain-0.5.0 → plain-0.7.0}/plain/utils/connection.py +0 -0
  116. {plain-0.5.0 → plain-0.7.0}/plain/utils/crypto.py +0 -0
  117. {plain-0.5.0 → plain-0.7.0}/plain/utils/datastructures.py +0 -0
  118. {plain-0.5.0 → plain-0.7.0}/plain/utils/dateformat.py +0 -0
  119. {plain-0.5.0 → plain-0.7.0}/plain/utils/dateparse.py +0 -0
  120. {plain-0.5.0 → plain-0.7.0}/plain/utils/dates.py +0 -0
  121. {plain-0.5.0 → plain-0.7.0}/plain/utils/deconstruct.py +0 -0
  122. {plain-0.5.0 → plain-0.7.0}/plain/utils/decorators.py +0 -0
  123. {plain-0.5.0 → plain-0.7.0}/plain/utils/deprecation.py +0 -0
  124. {plain-0.5.0 → plain-0.7.0}/plain/utils/duration.py +0 -0
  125. {plain-0.5.0 → plain-0.7.0}/plain/utils/email.py +0 -0
  126. {plain-0.5.0 → plain-0.7.0}/plain/utils/encoding.py +0 -0
  127. {plain-0.5.0 → plain-0.7.0}/plain/utils/functional.py +0 -0
  128. {plain-0.5.0 → plain-0.7.0}/plain/utils/hashable.py +0 -0
  129. {plain-0.5.0 → plain-0.7.0}/plain/utils/html.py +0 -0
  130. {plain-0.5.0 → plain-0.7.0}/plain/utils/http.py +0 -0
  131. {plain-0.5.0 → plain-0.7.0}/plain/utils/inspect.py +0 -0
  132. {plain-0.5.0 → plain-0.7.0}/plain/utils/ipv6.py +0 -0
  133. {plain-0.5.0 → plain-0.7.0}/plain/utils/itercompat.py +0 -0
  134. {plain-0.5.0 → plain-0.7.0}/plain/utils/module_loading.py +0 -0
  135. {plain-0.5.0 → plain-0.7.0}/plain/utils/regex_helper.py +0 -0
  136. {plain-0.5.0 → plain-0.7.0}/plain/utils/safestring.py +0 -0
  137. {plain-0.5.0 → plain-0.7.0}/plain/utils/termcolors.py +0 -0
  138. {plain-0.5.0 → plain-0.7.0}/plain/utils/text.py +0 -0
  139. {plain-0.5.0 → plain-0.7.0}/plain/utils/timesince.py +0 -0
  140. {plain-0.5.0 → plain-0.7.0}/plain/utils/tree.py +0 -0
  141. {plain-0.5.0 → plain-0.7.0}/plain/validators.py +0 -0
  142. {plain-0.5.0 → plain-0.7.0}/plain/views/README.md +0 -0
  143. {plain-0.5.0 → plain-0.7.0}/plain/views/__init__.py +0 -0
  144. {plain-0.5.0 → plain-0.7.0}/plain/views/base.py +0 -0
  145. {plain-0.5.0 → plain-0.7.0}/plain/views/csrf.py +0 -0
  146. {plain-0.5.0 → plain-0.7.0}/plain/views/errors.py +0 -0
  147. {plain-0.5.0 → plain-0.7.0}/plain/views/exceptions.py +0 -0
  148. {plain-0.5.0 → plain-0.7.0}/plain/views/forms.py +0 -0
  149. {plain-0.5.0 → plain-0.7.0}/plain/views/objects.py +0 -0
  150. {plain-0.5.0 → plain-0.7.0}/plain/views/redirect.py +0 -0
  151. {plain-0.5.0 → plain-0.7.0}/plain/views/templates.py +0 -0
  152. {plain-0.5.0 → plain-0.7.0}/plain/wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plain
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author: Dave Gaeddert
6
6
  Author-email: dave.gaeddert@dropseed.dev
@@ -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
- # TODO make this an entrypoint instead
288
- # Compile our Tailwind CSS (including templates in plain itself)
289
- if find_spec("plain.tailwind") is not None:
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 Exception as e:
470
+ except ImproperlyConfigured as e:
463
471
  click.secho(
464
- f"Error setting up Plain CLI\n{e}",
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 settings.USE_TZ and value is not None and timezone.is_naive(value):
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 settings.USE_TZ and value is not None and timezone.is_aware(value):
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]
@@ -59,7 +59,6 @@ MIDDLEWARE = [
59
59
  "plain.middleware.common.CommonMiddleware",
60
60
  "plain.csrf.middleware.CsrfViewMiddleware",
61
61
  "plain.auth.middleware.AuthenticationMiddleware",
62
- "plain.middleware.clickjacking.XFrameOptionsMiddleware",
63
62
  ]
64
63
 
65
64
  if DEBUG:
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  from dotenv import load_dotenv
7
7
 
8
- from .user_settings import LazySettings
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 = LazySettings()
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). When USE_TZ is True, this is
24
- # interpreted as the default user time zone.
25
- TIME_ZONE = "America/Chicago"
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
- SIGNING_BACKEND = "plain.signing.TimestampSigner"
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 = False
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 = False
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 #