We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Content Security Policy frame-src should not extend "none"
The default CSP `frame-src` setting [we configure is `["'none'"]`](https://github.com/cal-itp/benefits/blob/dev/benefits/settings.py#L275).

Then we `.extend()` that list with any additional allowed frame sources. But this is not correct in CSP, either `'none'` or a list of domains should be provided, not both.

We saw this with reCAPTCHA complaining in the console in `dev`:

![image](https://user-images.githubusercontent.com/1783439/195720177-9e2ad036-aaea-4256-8da2-e1d47565f67f.png)

## To Reproduce

Steps to reproduce the behavior:

1. Go to `dev`
2. Get to the Benefits selection page
3. Open the browser console
4. See error

## Expected behavior

A clear and concise description of what you expected to happen.

## Additional context

This _may_ be causing an issue where reCAPTCHA is allowing the form to submit even when required fields are not provided:

![no-field-validation](https://user-images.githubusercontent.com/1783439/195720727-33bbcfa8-f371-4f9b-9acf-f91f0c0eee7f.gif)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `benefits/settings.py`
Content:
```
1 """
2 Django settings for benefits project.
3 """
4 import os
5 import benefits.logging
6 
7 
8 def _filter_empty(ls):
9     return [s for s in ls if s]
10 
11 
12 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
13 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 
15 # SECURITY WARNING: keep the secret key used in production secret!
16 SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "secret")
17 
18 # SECURITY WARNING: don't run with debug turned on in production!
19 DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"
20 
21 ADMIN = os.environ.get("DJANGO_ADMIN", "False").lower() == "true"
22 
23 ALLOWED_HOSTS = _filter_empty(os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(","))
24 
25 # Application definition
26 
27 INSTALLED_APPS = [
28     "django.contrib.messages",
29     "django.contrib.sessions",
30     "django.contrib.staticfiles",
31     "benefits.core",
32     "benefits.enrollment",
33     "benefits.eligibility",
34     "benefits.oauth",
35 ]
36 
37 if ADMIN:
38     INSTALLED_APPS.extend(
39         [
40             "django.contrib.admin",
41             "django.contrib.auth",
42             "django.contrib.contenttypes",
43         ]
44     )
45 
46 MIDDLEWARE = [
47     "django.middleware.security.SecurityMiddleware",
48     "django.contrib.sessions.middleware.SessionMiddleware",
49     "django.contrib.messages.middleware.MessageMiddleware",
50     "django.middleware.locale.LocaleMiddleware",
51     "benefits.core.middleware.Healthcheck",
52     "django.middleware.common.CommonMiddleware",
53     "django.middleware.csrf.CsrfViewMiddleware",
54     "django.middleware.clickjacking.XFrameOptionsMiddleware",
55     "csp.middleware.CSPMiddleware",
56     "benefits.core.middleware.ChangedLanguageEvent",
57 ]
58 
59 if ADMIN:
60     MIDDLEWARE.extend(
61         [
62             "django.contrib.auth.middleware.AuthenticationMiddleware",
63             "django.contrib.messages.middleware.MessageMiddleware",
64         ]
65     )
66 
67 if DEBUG:
68     MIDDLEWARE.append("benefits.core.middleware.DebugSession")
69 
70 
71 # Azure Insights
72 # https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python-request#tracking-django-applications
73 
74 ENABLE_AZURE_INSIGHTS = "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ
75 print("ENABLE_AZURE_INSIGHTS: ", ENABLE_AZURE_INSIGHTS)
76 if ENABLE_AZURE_INSIGHTS:
77     MIDDLEWARE.extend(
78         [
79             "opencensus.ext.django.middleware.OpencensusMiddleware",
80             "benefits.core.middleware.LogErrorToAzure",
81         ]
82     )
83 
84 # only used if enabled above
85 OPENCENSUS = {
86     "TRACE": {
87         "SAMPLER": "opencensus.trace.samplers.ProbabilitySampler(rate=1)",
88         "EXPORTER": "opencensus.ext.azure.trace_exporter.AzureExporter()",
89     }
90 }
91 
92 
93 CSRF_COOKIE_AGE = None
94 CSRF_COOKIE_SAMESITE = "Strict"
95 CSRF_COOKIE_HTTPONLY = True
96 CSRF_TRUSTED_ORIGINS = _filter_empty(os.environ.get("DJANGO_TRUSTED_ORIGINS", "http://localhost,http://127.0.0.1").split(","))
97 
98 # With `Strict`, the user loses their Django session between leaving our app to
99 # sign in with OAuth, and coming back into our app from the OAuth redirect.
100 # This is because `Strict` disallows our cookie being sent from an external
101 # domain and so the session cookie is lost.
102 #
103 # `Lax` allows the cookie to travel with the user and be sent back to us by the
104 # OAuth server, as long as the request is "safe" i.e. GET
105 SESSION_COOKIE_SAMESITE = "Lax"
106 SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
107 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
108 SESSION_COOKIE_NAME = "_benefitssessionid"
109 
110 if not DEBUG:
111     CSRF_COOKIE_SECURE = True
112     CSRF_FAILURE_VIEW = "benefits.core.views.csrf_failure"
113     SESSION_COOKIE_SECURE = True
114 
115 SECURE_BROWSER_XSS_FILTER = True
116 
117 # required so that cross-origin pop-ups (like the enrollment overlay) have access to parent window context
118 # https://github.com/cal-itp/benefits/pull/793
119 SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin-allow-popups"
120 
121 # the NGINX reverse proxy sits in front of the application in deployed environments
122 # SSL terminates before getting to Django, and NGINX adds this header to indicate
123 # if the original request was secure or not
124 #
125 # See https://docs.djangoproject.com/en/4.0/ref/settings/#secure-proxy-ssl-header
126 if not DEBUG:
127     SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
128 
129 ROOT_URLCONF = "benefits.urls"
130 
131 template_ctx_processors = [
132     "django.template.context_processors.request",
133     "django.contrib.messages.context_processors.messages",
134     "benefits.core.context_processors.analytics",
135     "benefits.core.context_processors.authentication",
136     "benefits.core.context_processors.recaptcha",
137 ]
138 
139 if DEBUG:
140     template_ctx_processors.extend(
141         [
142             "django.template.context_processors.debug",
143             "benefits.core.context_processors.debug",
144         ]
145     )
146 
147 if ADMIN:
148     template_ctx_processors.extend(
149         [
150             "django.contrib.auth.context_processors.auth",
151             "django.contrib.messages.context_processors.messages",
152         ]
153     )
154 
155 TEMPLATES = [
156     {
157         "BACKEND": "django.template.backends.django.DjangoTemplates",
158         "DIRS": [os.path.join(BASE_DIR, "benefits", "templates")],
159         "APP_DIRS": True,
160         "OPTIONS": {
161             "context_processors": template_ctx_processors,
162         },
163     },
164 ]
165 
166 WSGI_APPLICATION = "benefits.wsgi.application"
167 
168 DATABASES = {
169     "default": {
170         "ENGINE": "django.db.backends.sqlite3",
171         "NAME": "django.db",
172     }
173 }
174 
175 LOAD_SAMPLE_DATA = os.environ.get("DJANGO_LOAD_SAMPLE_DATA", "true").lower() != "false"
176 
177 # Password validation
178 
179 AUTH_PASSWORD_VALIDATORS = []
180 
181 if ADMIN:
182     AUTH_PASSWORD_VALIDATORS.extend(
183         [
184             {
185                 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
186             },
187             {
188                 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
189             },
190             {
191                 "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
192             },
193             {
194                 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
195             },
196         ]
197     )
198 
199 # Internationalization
200 
201 LANGUAGE_CODE = "en"
202 
203 LANGUAGE_COOKIE_HTTPONLY = True
204 LANGUAGE_COOKIE_SAMESITE = "Strict"
205 LANGUAGE_COOKIE_SECURE = True
206 
207 LANGUAGES = [("en", "English"), ("es", "Español")]
208 
209 LOCALE_PATHS = [os.path.join(BASE_DIR, "benefits", "locale")]
210 
211 USE_I18N = True
212 USE_L10N = True
213 
214 TIME_ZONE = "UTC"
215 USE_TZ = True
216 
217 # Static files (CSS, JavaScript, Images)
218 
219 STATIC_URL = "/static/"
220 STATICFILES_DIRS = [os.path.join(BASE_DIR, "benefits", "static")]
221 # use Manifest Static Files Storage by default
222 STATICFILES_STORAGE = os.environ.get(
223     "DJANGO_STATICFILES_STORAGE", "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
224 )
225 STATIC_ROOT = os.path.join(BASE_DIR, "static")
226 
227 # Logging configuration
228 LOG_LEVEL = os.environ.get("DJANGO_LOG_LEVEL", "DEBUG" if DEBUG else "WARNING")
229 LOGGING = benefits.logging.get_config(LOG_LEVEL, enable_azure=ENABLE_AZURE_INSIGHTS)
230 
231 # Analytics configuration
232 
233 ANALYTICS_KEY = os.environ.get("ANALYTICS_KEY")
234 
235 # rate limit configuration
236 
237 # number of requests allowed in the given period
238 RATE_LIMIT = int(os.environ.get("DJANGO_RATE_LIMIT", 5))
239 
240 # HTTP request methods to rate limit
241 RATE_LIMIT_METHODS = os.environ.get("DJANGO_RATE_LIMIT_METHODS", "POST").upper().split(",")
242 
243 # number of seconds before additional requests are denied
244 RATE_LIMIT_PERIOD = int(os.environ.get("DJANGO_RATE_LIMIT_PERIOD", 60))
245 
246 # Rate Limit feature flag
247 RATE_LIMIT_ENABLED = all((RATE_LIMIT > 0, len(RATE_LIMIT_METHODS) > 0, RATE_LIMIT_PERIOD > 0))
248 
249 # reCAPTCHA configuration
250 
251 RECAPTCHA_API_URL = os.environ.get("DJANGO_RECAPTCHA_API_URL", "https://www.google.com/recaptcha/api.js")
252 RECAPTCHA_SITE_KEY = os.environ.get("DJANGO_RECAPTCHA_SITE_KEY")
253 RECAPTCHA_SECRET_KEY = os.environ.get("DJANGO_RECAPTCHA_SECRET_KEY")
254 RECAPTCHA_VERIFY_URL = os.environ.get("DJANGO_RECAPTCHA_VERIFY_URL", "https://www.google.com/recaptcha/api/siteverify")
255 RECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))
256 
257 # Content Security Policy
258 # Configuration docs at https://django-csp.readthedocs.io/en/latest/configuration.html
259 
260 # In particular, note that the inner single-quotes are required!
261 # https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings
262 
263 CSP_DEFAULT_SRC = ["'self'"]
264 
265 CSP_CONNECT_SRC = ["'self'", "https://api.amplitude.com/"]
266 env_connect_src = _filter_empty(os.environ.get("DJANGO_CSP_CONNECT_SRC", "").split(","))
267 CSP_CONNECT_SRC.extend(env_connect_src)
268 
269 CSP_FONT_SRC = ["'self'", "https://california.azureedge.net/", "https://fonts.gstatic.com/"]
270 env_font_src = _filter_empty(os.environ.get("DJANGO_CSP_FONT_SRC", "").split(","))
271 CSP_FONT_SRC.extend(env_font_src)
272 
273 CSP_FRAME_ANCESTORS = ["'none'"]
274 
275 CSP_FRAME_SRC = ["'none'"]
276 env_frame_src = _filter_empty(os.environ.get("DJANGO_CSP_FRAME_SRC", "").split(","))
277 CSP_FRAME_SRC.extend(env_frame_src)
278 if RECAPTCHA_ENABLED:
279     CSP_FRAME_SRC.append("https://www.google.com")
280 
281 
282 CSP_SCRIPT_SRC = [
283     "'unsafe-inline'",
284     "https://california.azureedge.net/",
285     "https://cdn.amplitude.com/libs/",
286     "https://code.jquery.com/",
287     "*.littlepay.com",
288 ]
289 env_script_src = _filter_empty(os.environ.get("DJANGO_CSP_SCRIPT_SRC", "").split(","))
290 CSP_SCRIPT_SRC.extend(env_script_src)
291 if RECAPTCHA_ENABLED:
292     CSP_SCRIPT_SRC.extend(["https://www.google.com/recaptcha/", "https://www.gstatic.com/recaptcha/releases/"])
293 
294 CSP_STYLE_SRC = [
295     "'self'",
296     "'unsafe-inline'",
297     "https://california.azureedge.net/",
298     "https://fonts.googleapis.com/css",
299 ]
300 env_style_src = _filter_empty(os.environ.get("DJANGO_CSP_STYLE_SRC", "").split(","))
301 CSP_STYLE_SRC.extend(env_style_src)
302
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/benefits/settings.py b/benefits/settings.py
--- a/benefits/settings.py
+++ b/benefits/settings.py
@@ -274,10 +274,10 @@
 
 CSP_FRAME_SRC = ["'none'"]
 env_frame_src = _filter_empty(os.environ.get("DJANGO_CSP_FRAME_SRC", "").split(","))
-CSP_FRAME_SRC.extend(env_frame_src)
 if RECAPTCHA_ENABLED:
-    CSP_FRAME_SRC.append("https://www.google.com")
-
+    env_frame_src.append("https://www.google.com")
+if len(env_frame_src) > 0:
+    CSP_FRAME_SRC = env_frame_src
 
 CSP_SCRIPT_SRC = [
     "'unsafe-inline'",

{"golden_diff": "diff --git a/benefits/settings.py b/benefits/settings.py\n--- a/benefits/settings.py\n+++ b/benefits/settings.py\n@@ -274,10 +274,10 @@\n \n CSP_FRAME_SRC = [\"'none'\"]\n env_frame_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FRAME_SRC\", \"\").split(\",\"))\n-CSP_FRAME_SRC.extend(env_frame_src)\n if RECAPTCHA_ENABLED:\n-    CSP_FRAME_SRC.append(\"https://www.google.com\")\n-\n+    env_frame_src.append(\"https://www.google.com\")\n+if len(env_frame_src) > 0:\n+    CSP_FRAME_SRC = env_frame_src\n \n CSP_SCRIPT_SRC = [\n     \"'unsafe-inline'\",\n", "issue": "Content Security Policy frame-src should not extend \"none\"\nThe default CSP `frame-src` setting [we configure is `[\"'none'\"]`](https://github.com/cal-itp/benefits/blob/dev/benefits/settings.py#L275).\r\n\r\nThen we `.extend()` that list with any additional allowed frame sources. But this is not correct in CSP, either `'none'` or a list of domains should be provided, not both.\r\n\r\nWe saw this with reCAPTCHA complaining in the console in `dev`:\r\n\r\n![image](https://user-images.githubusercontent.com/1783439/195720177-9e2ad036-aaea-4256-8da2-e1d47565f67f.png)\r\n\r\n## To Reproduce\r\n\r\nSteps to reproduce the behavior:\r\n\r\n1. Go to `dev`\r\n2. Get to the Benefits selection page\r\n3. Open the browser console\r\n4. See error\r\n\r\n## Expected behavior\r\n\r\nA clear and concise description of what you expected to happen.\r\n\r\n## Additional context\r\n\r\nThis _may_ be causing an issue where reCAPTCHA is allowing the form to submit even when required fields are not provided:\r\n\r\n![no-field-validation](https://user-images.githubusercontent.com/1783439/195720727-33bbcfa8-f371-4f9b-9acf-f91f0c0eee7f.gif)\r\n\n", "before_files": [{"content": "\"\"\"\nDjango settings for benefits project.\n\"\"\"\nimport os\nimport benefits.logging\n\n\ndef _filter_empty(ls):\n    return [s for s in ls if s]\n\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = os.environ.get(\"DJANGO_SECRET_KEY\", \"secret\")\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = os.environ.get(\"DJANGO_DEBUG\", \"False\").lower() == \"true\"\n\nADMIN = os.environ.get(\"DJANGO_ADMIN\", \"False\").lower() == \"true\"\n\nALLOWED_HOSTS = _filter_empty(os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"localhost,127.0.0.1\").split(\",\"))\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.messages\",\n    \"django.contrib.sessions\",\n    \"django.contrib.staticfiles\",\n    \"benefits.core\",\n    \"benefits.enrollment\",\n    \"benefits.eligibility\",\n    \"benefits.oauth\",\n]\n\nif ADMIN:\n    INSTALLED_APPS.extend(\n        [\n            \"django.contrib.admin\",\n            \"django.contrib.auth\",\n            \"django.contrib.contenttypes\",\n        ]\n    )\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.locale.LocaleMiddleware\",\n    \"benefits.core.middleware.Healthcheck\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n    \"csp.middleware.CSPMiddleware\",\n    \"benefits.core.middleware.ChangedLanguageEvent\",\n]\n\nif ADMIN:\n    MIDDLEWARE.extend(\n        [\n            \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n            \"django.contrib.messages.middleware.MessageMiddleware\",\n        ]\n    )\n\nif DEBUG:\n    MIDDLEWARE.append(\"benefits.core.middleware.DebugSession\")\n\n\n# Azure Insights\n# https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python-request#tracking-django-applications\n\nENABLE_AZURE_INSIGHTS = \"APPLICATIONINSIGHTS_CONNECTION_STRING\" in os.environ\nprint(\"ENABLE_AZURE_INSIGHTS: \", ENABLE_AZURE_INSIGHTS)\nif ENABLE_AZURE_INSIGHTS:\n    MIDDLEWARE.extend(\n        [\n            \"opencensus.ext.django.middleware.OpencensusMiddleware\",\n            \"benefits.core.middleware.LogErrorToAzure\",\n        ]\n    )\n\n# only used if enabled above\nOPENCENSUS = {\n    \"TRACE\": {\n        \"SAMPLER\": \"opencensus.trace.samplers.ProbabilitySampler(rate=1)\",\n        \"EXPORTER\": \"opencensus.ext.azure.trace_exporter.AzureExporter()\",\n    }\n}\n\n\nCSRF_COOKIE_AGE = None\nCSRF_COOKIE_SAMESITE = \"Strict\"\nCSRF_COOKIE_HTTPONLY = True\nCSRF_TRUSTED_ORIGINS = _filter_empty(os.environ.get(\"DJANGO_TRUSTED_ORIGINS\", \"http://localhost,http://127.0.0.1\").split(\",\"))\n\n# With `Strict`, the user loses their Django session between leaving our app to\n# sign in with OAuth, and coming back into our app from the OAuth redirect.\n# This is because `Strict` disallows our cookie being sent from an external\n# domain and so the session cookie is lost.\n#\n# `Lax` allows the cookie to travel with the user and be sent back to us by the\n# OAuth server, as long as the request is \"safe\" i.e. GET\nSESSION_COOKIE_SAMESITE = \"Lax\"\nSESSION_ENGINE = \"django.contrib.sessions.backends.signed_cookies\"\nSESSION_EXPIRE_AT_BROWSER_CLOSE = True\nSESSION_COOKIE_NAME = \"_benefitssessionid\"\n\nif not DEBUG:\n    CSRF_COOKIE_SECURE = True\n    CSRF_FAILURE_VIEW = \"benefits.core.views.csrf_failure\"\n    SESSION_COOKIE_SECURE = True\n\nSECURE_BROWSER_XSS_FILTER = True\n\n# required so that cross-origin pop-ups (like the enrollment overlay) have access to parent window context\n# https://github.com/cal-itp/benefits/pull/793\nSECURE_CROSS_ORIGIN_OPENER_POLICY = \"same-origin-allow-popups\"\n\n# the NGINX reverse proxy sits in front of the application in deployed environments\n# SSL terminates before getting to Django, and NGINX adds this header to indicate\n# if the original request was secure or not\n#\n# See https://docs.djangoproject.com/en/4.0/ref/settings/#secure-proxy-ssl-header\nif not DEBUG:\n    SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\nROOT_URLCONF = \"benefits.urls\"\n\ntemplate_ctx_processors = [\n    \"django.template.context_processors.request\",\n    \"django.contrib.messages.context_processors.messages\",\n    \"benefits.core.context_processors.analytics\",\n    \"benefits.core.context_processors.authentication\",\n    \"benefits.core.context_processors.recaptcha\",\n]\n\nif DEBUG:\n    template_ctx_processors.extend(\n        [\n            \"django.template.context_processors.debug\",\n            \"benefits.core.context_processors.debug\",\n        ]\n    )\n\nif ADMIN:\n    template_ctx_processors.extend(\n        [\n            \"django.contrib.auth.context_processors.auth\",\n            \"django.contrib.messages.context_processors.messages\",\n        ]\n    )\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [os.path.join(BASE_DIR, \"benefits\", \"templates\")],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": template_ctx_processors,\n        },\n    },\n]\n\nWSGI_APPLICATION = \"benefits.wsgi.application\"\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": \"django.db\",\n    }\n}\n\nLOAD_SAMPLE_DATA = os.environ.get(\"DJANGO_LOAD_SAMPLE_DATA\", \"true\").lower() != \"false\"\n\n# Password validation\n\nAUTH_PASSWORD_VALIDATORS = []\n\nif ADMIN:\n    AUTH_PASSWORD_VALIDATORS.extend(\n        [\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n            },\n        ]\n    )\n\n# Internationalization\n\nLANGUAGE_CODE = \"en\"\n\nLANGUAGE_COOKIE_HTTPONLY = True\nLANGUAGE_COOKIE_SAMESITE = \"Strict\"\nLANGUAGE_COOKIE_SECURE = True\n\nLANGUAGES = [(\"en\", \"English\"), (\"es\", \"Espa\u00f1ol\")]\n\nLOCALE_PATHS = [os.path.join(BASE_DIR, \"benefits\", \"locale\")]\n\nUSE_I18N = True\nUSE_L10N = True\n\nTIME_ZONE = \"UTC\"\nUSE_TZ = True\n\n# Static files (CSS, JavaScript, Images)\n\nSTATIC_URL = \"/static/\"\nSTATICFILES_DIRS = [os.path.join(BASE_DIR, \"benefits\", \"static\")]\n# use Manifest Static Files Storage by default\nSTATICFILES_STORAGE = os.environ.get(\n    \"DJANGO_STATICFILES_STORAGE\", \"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\"\n)\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\n# Logging configuration\nLOG_LEVEL = os.environ.get(\"DJANGO_LOG_LEVEL\", \"DEBUG\" if DEBUG else \"WARNING\")\nLOGGING = benefits.logging.get_config(LOG_LEVEL, enable_azure=ENABLE_AZURE_INSIGHTS)\n\n# Analytics configuration\n\nANALYTICS_KEY = os.environ.get(\"ANALYTICS_KEY\")\n\n# rate limit configuration\n\n# number of requests allowed in the given period\nRATE_LIMIT = int(os.environ.get(\"DJANGO_RATE_LIMIT\", 5))\n\n# HTTP request methods to rate limit\nRATE_LIMIT_METHODS = os.environ.get(\"DJANGO_RATE_LIMIT_METHODS\", \"POST\").upper().split(\",\")\n\n# number of seconds before additional requests are denied\nRATE_LIMIT_PERIOD = int(os.environ.get(\"DJANGO_RATE_LIMIT_PERIOD\", 60))\n\n# Rate Limit feature flag\nRATE_LIMIT_ENABLED = all((RATE_LIMIT > 0, len(RATE_LIMIT_METHODS) > 0, RATE_LIMIT_PERIOD > 0))\n\n# reCAPTCHA configuration\n\nRECAPTCHA_API_URL = os.environ.get(\"DJANGO_RECAPTCHA_API_URL\", \"https://www.google.com/recaptcha/api.js\")\nRECAPTCHA_SITE_KEY = os.environ.get(\"DJANGO_RECAPTCHA_SITE_KEY\")\nRECAPTCHA_SECRET_KEY = os.environ.get(\"DJANGO_RECAPTCHA_SECRET_KEY\")\nRECAPTCHA_VERIFY_URL = os.environ.get(\"DJANGO_RECAPTCHA_VERIFY_URL\", \"https://www.google.com/recaptcha/api/siteverify\")\nRECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))\n\n# Content Security Policy\n# Configuration docs at https://django-csp.readthedocs.io/en/latest/configuration.html\n\n# In particular, note that the inner single-quotes are required!\n# https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings\n\nCSP_DEFAULT_SRC = [\"'self'\"]\n\nCSP_CONNECT_SRC = [\"'self'\", \"https://api.amplitude.com/\"]\nenv_connect_src = _filter_empty(os.environ.get(\"DJANGO_CSP_CONNECT_SRC\", \"\").split(\",\"))\nCSP_CONNECT_SRC.extend(env_connect_src)\n\nCSP_FONT_SRC = [\"'self'\", \"https://california.azureedge.net/\", \"https://fonts.gstatic.com/\"]\nenv_font_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FONT_SRC\", \"\").split(\",\"))\nCSP_FONT_SRC.extend(env_font_src)\n\nCSP_FRAME_ANCESTORS = [\"'none'\"]\n\nCSP_FRAME_SRC = [\"'none'\"]\nenv_frame_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FRAME_SRC\", \"\").split(\",\"))\nCSP_FRAME_SRC.extend(env_frame_src)\nif RECAPTCHA_ENABLED:\n    CSP_FRAME_SRC.append(\"https://www.google.com\")\n\n\nCSP_SCRIPT_SRC = [\n    \"'unsafe-inline'\",\n    \"https://california.azureedge.net/\",\n    \"https://cdn.amplitude.com/libs/\",\n    \"https://code.jquery.com/\",\n    \"*.littlepay.com\",\n]\nenv_script_src = _filter_empty(os.environ.get(\"DJANGO_CSP_SCRIPT_SRC\", \"\").split(\",\"))\nCSP_SCRIPT_SRC.extend(env_script_src)\nif RECAPTCHA_ENABLED:\n    CSP_SCRIPT_SRC.extend([\"https://www.google.com/recaptcha/\", \"https://www.gstatic.com/recaptcha/releases/\"])\n\nCSP_STYLE_SRC = [\n    \"'self'\",\n    \"'unsafe-inline'\",\n    \"https://california.azureedge.net/\",\n    \"https://fonts.googleapis.com/css\",\n]\nenv_style_src = _filter_empty(os.environ.get(\"DJANGO_CSP_STYLE_SRC\", \"\").split(\",\"))\nCSP_STYLE_SRC.extend(env_style_src)\n", "path": "benefits/settings.py"}], "after_files": [{"content": "\"\"\"\nDjango settings for benefits project.\n\"\"\"\nimport os\nimport benefits.logging\n\n\ndef _filter_empty(ls):\n    return [s for s in ls if s]\n\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = os.environ.get(\"DJANGO_SECRET_KEY\", \"secret\")\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = os.environ.get(\"DJANGO_DEBUG\", \"False\").lower() == \"true\"\n\nADMIN = os.environ.get(\"DJANGO_ADMIN\", \"False\").lower() == \"true\"\n\nALLOWED_HOSTS = _filter_empty(os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"localhost,127.0.0.1\").split(\",\"))\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.messages\",\n    \"django.contrib.sessions\",\n    \"django.contrib.staticfiles\",\n    \"benefits.core\",\n    \"benefits.enrollment\",\n    \"benefits.eligibility\",\n    \"benefits.oauth\",\n]\n\nif ADMIN:\n    INSTALLED_APPS.extend(\n        [\n            \"django.contrib.admin\",\n            \"django.contrib.auth\",\n            \"django.contrib.contenttypes\",\n        ]\n    )\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.locale.LocaleMiddleware\",\n    \"benefits.core.middleware.Healthcheck\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n    \"csp.middleware.CSPMiddleware\",\n    \"benefits.core.middleware.ChangedLanguageEvent\",\n]\n\nif ADMIN:\n    MIDDLEWARE.extend(\n        [\n            \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n            \"django.contrib.messages.middleware.MessageMiddleware\",\n        ]\n    )\n\nif DEBUG:\n    MIDDLEWARE.append(\"benefits.core.middleware.DebugSession\")\n\n\n# Azure Insights\n# https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python-request#tracking-django-applications\n\nENABLE_AZURE_INSIGHTS = \"APPLICATIONINSIGHTS_CONNECTION_STRING\" in os.environ\nprint(\"ENABLE_AZURE_INSIGHTS: \", ENABLE_AZURE_INSIGHTS)\nif ENABLE_AZURE_INSIGHTS:\n    MIDDLEWARE.extend(\n        [\n            \"opencensus.ext.django.middleware.OpencensusMiddleware\",\n            \"benefits.core.middleware.LogErrorToAzure\",\n        ]\n    )\n\n# only used if enabled above\nOPENCENSUS = {\n    \"TRACE\": {\n        \"SAMPLER\": \"opencensus.trace.samplers.ProbabilitySampler(rate=1)\",\n        \"EXPORTER\": \"opencensus.ext.azure.trace_exporter.AzureExporter()\",\n    }\n}\n\n\nCSRF_COOKIE_AGE = None\nCSRF_COOKIE_SAMESITE = \"Strict\"\nCSRF_COOKIE_HTTPONLY = True\nCSRF_TRUSTED_ORIGINS = _filter_empty(os.environ.get(\"DJANGO_TRUSTED_ORIGINS\", \"http://localhost,http://127.0.0.1\").split(\",\"))\n\n# With `Strict`, the user loses their Django session between leaving our app to\n# sign in with OAuth, and coming back into our app from the OAuth redirect.\n# This is because `Strict` disallows our cookie being sent from an external\n# domain and so the session cookie is lost.\n#\n# `Lax` allows the cookie to travel with the user and be sent back to us by the\n# OAuth server, as long as the request is \"safe\" i.e. GET\nSESSION_COOKIE_SAMESITE = \"Lax\"\nSESSION_ENGINE = \"django.contrib.sessions.backends.signed_cookies\"\nSESSION_EXPIRE_AT_BROWSER_CLOSE = True\nSESSION_COOKIE_NAME = \"_benefitssessionid\"\n\nif not DEBUG:\n    CSRF_COOKIE_SECURE = True\n    CSRF_FAILURE_VIEW = \"benefits.core.views.csrf_failure\"\n    SESSION_COOKIE_SECURE = True\n\nSECURE_BROWSER_XSS_FILTER = True\n\n# required so that cross-origin pop-ups (like the enrollment overlay) have access to parent window context\n# https://github.com/cal-itp/benefits/pull/793\nSECURE_CROSS_ORIGIN_OPENER_POLICY = \"same-origin-allow-popups\"\n\n# the NGINX reverse proxy sits in front of the application in deployed environments\n# SSL terminates before getting to Django, and NGINX adds this header to indicate\n# if the original request was secure or not\n#\n# See https://docs.djangoproject.com/en/4.0/ref/settings/#secure-proxy-ssl-header\nif not DEBUG:\n    SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\nROOT_URLCONF = \"benefits.urls\"\n\ntemplate_ctx_processors = [\n    \"django.template.context_processors.request\",\n    \"django.contrib.messages.context_processors.messages\",\n    \"benefits.core.context_processors.analytics\",\n    \"benefits.core.context_processors.authentication\",\n    \"benefits.core.context_processors.recaptcha\",\n]\n\nif DEBUG:\n    template_ctx_processors.extend(\n        [\n            \"django.template.context_processors.debug\",\n            \"benefits.core.context_processors.debug\",\n        ]\n    )\n\nif ADMIN:\n    template_ctx_processors.extend(\n        [\n            \"django.contrib.auth.context_processors.auth\",\n            \"django.contrib.messages.context_processors.messages\",\n        ]\n    )\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [os.path.join(BASE_DIR, \"benefits\", \"templates\")],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": template_ctx_processors,\n        },\n    },\n]\n\nWSGI_APPLICATION = \"benefits.wsgi.application\"\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": \"django.db\",\n    }\n}\n\nLOAD_SAMPLE_DATA = os.environ.get(\"DJANGO_LOAD_SAMPLE_DATA\", \"true\").lower() != \"false\"\n\n# Password validation\n\nAUTH_PASSWORD_VALIDATORS = []\n\nif ADMIN:\n    AUTH_PASSWORD_VALIDATORS.extend(\n        [\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n            },\n            {\n                \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n            },\n        ]\n    )\n\n# Internationalization\n\nLANGUAGE_CODE = \"en\"\n\nLANGUAGE_COOKIE_HTTPONLY = True\nLANGUAGE_COOKIE_SAMESITE = \"Strict\"\nLANGUAGE_COOKIE_SECURE = True\n\nLANGUAGES = [(\"en\", \"English\"), (\"es\", \"Espa\u00f1ol\")]\n\nLOCALE_PATHS = [os.path.join(BASE_DIR, \"benefits\", \"locale\")]\n\nUSE_I18N = True\nUSE_L10N = True\n\nTIME_ZONE = \"UTC\"\nUSE_TZ = True\n\n# Static files (CSS, JavaScript, Images)\n\nSTATIC_URL = \"/static/\"\nSTATICFILES_DIRS = [os.path.join(BASE_DIR, \"benefits\", \"static\")]\n# use Manifest Static Files Storage by default\nSTATICFILES_STORAGE = os.environ.get(\n    \"DJANGO_STATICFILES_STORAGE\", \"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\"\n)\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\n# Logging configuration\nLOG_LEVEL = os.environ.get(\"DJANGO_LOG_LEVEL\", \"DEBUG\" if DEBUG else \"WARNING\")\nLOGGING = benefits.logging.get_config(LOG_LEVEL, enable_azure=ENABLE_AZURE_INSIGHTS)\n\n# Analytics configuration\n\nANALYTICS_KEY = os.environ.get(\"ANALYTICS_KEY\")\n\n# rate limit configuration\n\n# number of requests allowed in the given period\nRATE_LIMIT = int(os.environ.get(\"DJANGO_RATE_LIMIT\", 5))\n\n# HTTP request methods to rate limit\nRATE_LIMIT_METHODS = os.environ.get(\"DJANGO_RATE_LIMIT_METHODS\", \"POST\").upper().split(\",\")\n\n# number of seconds before additional requests are denied\nRATE_LIMIT_PERIOD = int(os.environ.get(\"DJANGO_RATE_LIMIT_PERIOD\", 60))\n\n# Rate Limit feature flag\nRATE_LIMIT_ENABLED = all((RATE_LIMIT > 0, len(RATE_LIMIT_METHODS) > 0, RATE_LIMIT_PERIOD > 0))\n\n# reCAPTCHA configuration\n\nRECAPTCHA_API_URL = os.environ.get(\"DJANGO_RECAPTCHA_API_URL\", \"https://www.google.com/recaptcha/api.js\")\nRECAPTCHA_SITE_KEY = os.environ.get(\"DJANGO_RECAPTCHA_SITE_KEY\")\nRECAPTCHA_SECRET_KEY = os.environ.get(\"DJANGO_RECAPTCHA_SECRET_KEY\")\nRECAPTCHA_VERIFY_URL = os.environ.get(\"DJANGO_RECAPTCHA_VERIFY_URL\", \"https://www.google.com/recaptcha/api/siteverify\")\nRECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))\n\n# Content Security Policy\n# Configuration docs at https://django-csp.readthedocs.io/en/latest/configuration.html\n\n# In particular, note that the inner single-quotes are required!\n# https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings\n\nCSP_DEFAULT_SRC = [\"'self'\"]\n\nCSP_CONNECT_SRC = [\"'self'\", \"https://api.amplitude.com/\"]\nenv_connect_src = _filter_empty(os.environ.get(\"DJANGO_CSP_CONNECT_SRC\", \"\").split(\",\"))\nCSP_CONNECT_SRC.extend(env_connect_src)\n\nCSP_FONT_SRC = [\"'self'\", \"https://california.azureedge.net/\", \"https://fonts.gstatic.com/\"]\nenv_font_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FONT_SRC\", \"\").split(\",\"))\nCSP_FONT_SRC.extend(env_font_src)\n\nCSP_FRAME_ANCESTORS = [\"'none'\"]\n\nCSP_FRAME_SRC = [\"'none'\"]\nenv_frame_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FRAME_SRC\", \"\").split(\",\"))\nif RECAPTCHA_ENABLED:\n    env_frame_src.append(\"https://www.google.com\")\nif len(env_frame_src) > 0:\n    CSP_FRAME_SRC = env_frame_src\n\nCSP_SCRIPT_SRC = [\n    \"'unsafe-inline'\",\n    \"https://california.azureedge.net/\",\n    \"https://cdn.amplitude.com/libs/\",\n    \"https://code.jquery.com/\",\n    \"*.littlepay.com\",\n]\nenv_script_src = _filter_empty(os.environ.get(\"DJANGO_CSP_SCRIPT_SRC\", \"\").split(\",\"))\nCSP_SCRIPT_SRC.extend(env_script_src)\nif RECAPTCHA_ENABLED:\n    CSP_SCRIPT_SRC.extend([\"https://www.google.com/recaptcha/\", \"https://www.gstatic.com/recaptcha/releases/\"])\n\nCSP_STYLE_SRC = [\n    \"'self'\",\n    \"'unsafe-inline'\",\n    \"https://california.azureedge.net/\",\n    \"https://fonts.googleapis.com/css\",\n]\nenv_style_src = _filter_empty(os.environ.get(\"DJANGO_CSP_STYLE_SRC\", \"\").split(\",\"))\nCSP_STYLE_SRC.extend(env_style_src)\n", "path": "benefits/settings.py"}]}