We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Ensure OAuth redirect URLs are secure
We construct the OAUth redirect URLs using the Django [`reverse()`](https://docs.djangoproject.com/en/3.2/ref/urlresolvers/#reverse) helper and [`request.build_absolute_uri()`](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri).

This is done in e.g. the [`login()` view](https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py):

```python
ROUTE_AUTH = "oauth:authorize"

route = reverse(ROUTE_AUTH)

redirect_uri = request.build_absolute_uri(route)
```

The docs for `build_absolute_uri` say:

> Mixing HTTP and HTTPS on the same site is discouraged, therefore [build_absolute_uri()](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri) will always generate an absolute URI with the same scheme the current request has. If you need to redirect users to HTTPS, it’s best to let your Web server redirect all HTTP traffic to HTTPS.

When running in AWS, we have the Load Balancer sitting in front of all requests; this is the layer where HTTP --> HTTPS redirection happens and where the cert that the browser sees is installed.

Then the request gets to the application container, where it first hits `nginx`. This is where requests for static files end; application requests are forwarded through to `gunicorn` over a Unix socket. Strictly speaking, Django does not receive an HTTPS request.

## Potential solution

The [`SECURE_PROXY_SSL_HEADER`](https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header) Django setting seems to be aimed at helping this situation, where `nginx` sets a header with an indicated value on the request forwarded into the application via gunicorn, to tell the app if the request was originally secure or not.

In the app's [`nginx.conf`](https://github.com/cal-itp/benefits/blob/dev/nginx.conf#L55) we do set that header on forwarded requests.

## Todo

* [x] Ensure the current `nginx.conf` setting overwrites any header value coming in with the request and/or clear that header first
* [x] Add the `SECURE_PROXY_SSL_HEADER` to `settings.py` when `DEBUG = False`

--- 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 
6 
7 def _filter_empty(ls):
8     return [s for s in ls if s]
9 
10 
11 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13 
14 # SECURITY WARNING: keep the secret key used in production secret!
15 SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
16 
17 # SECURITY WARNING: don't run with debug turned on in production!
18 DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"
19 
20 ADMIN = os.environ.get("DJANGO_ADMIN", "False").lower() == "true"
21 
22 ALLOWED_HOSTS = _filter_empty(os.environ["DJANGO_ALLOWED_HOSTS"].split(","))
23 
24 # Application definition
25 
26 INSTALLED_APPS = [
27     "django.contrib.messages",
28     "django.contrib.sessions",
29     "django.contrib.staticfiles",
30     "benefits.core",
31     "benefits.enrollment",
32     "benefits.eligibility",
33     "benefits.oauth",
34 ]
35 
36 if ADMIN:
37     INSTALLED_APPS.extend(
38         [
39             "django.contrib.admin",
40             "django.contrib.auth",
41             "django.contrib.contenttypes",
42         ]
43     )
44 
45 MIDDLEWARE = [
46     "django.middleware.security.SecurityMiddleware",
47     "django.contrib.sessions.middleware.SessionMiddleware",
48     "django.contrib.messages.middleware.MessageMiddleware",
49     "django.middleware.locale.LocaleMiddleware",
50     "benefits.core.middleware.Healthcheck",
51     "django.middleware.common.CommonMiddleware",
52     "django.middleware.csrf.CsrfViewMiddleware",
53     "django.middleware.clickjacking.XFrameOptionsMiddleware",
54     "csp.middleware.CSPMiddleware",
55     "benefits.core.middleware.ChangedLanguageEvent",
56 ]
57 
58 if ADMIN:
59     MIDDLEWARE.extend(
60         [
61             "django.contrib.auth.middleware.AuthenticationMiddleware",
62             "django.contrib.messages.middleware.MessageMiddleware",
63         ]
64     )
65 
66 if DEBUG:
67     MIDDLEWARE.extend(["benefits.core.middleware.DebugSession"])
68 
69 CSRF_COOKIE_AGE = None
70 CSRF_COOKIE_SAMESITE = "Strict"
71 CSRF_COOKIE_HTTPONLY = True
72 CSRF_TRUSTED_ORIGINS = _filter_empty(os.environ["DJANGO_TRUSTED_ORIGINS"].split(","))
73 
74 # With `Strict`, the user loses their Django session between leaving our app to
75 # sign in with OAuth, and coming back into our app from the OAuth redirect.
76 # This is because `Strict` disallows our cookie being sent from an external
77 # domain and so the session cookie is lost.
78 #
79 # `Lax` allows the cookie to travel with the user and be sent back to us by the
80 # OAuth server, as long as the request is "safe" i.e. GET
81 SESSION_COOKIE_SAMESITE = "Lax"
82 SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
83 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
84 SESSION_COOKIE_NAME = "_benefitssessionid"
85 
86 if not DEBUG:
87     CSRF_COOKIE_SECURE = True
88     CSRF_FAILURE_VIEW = "benefits.core.views.csrf_failure"
89     SESSION_COOKIE_SECURE = True
90 
91 SECURE_BROWSER_XSS_FILTER = True
92 
93 ROOT_URLCONF = "benefits.urls"
94 
95 template_ctx_processors = [
96     "django.template.context_processors.request",
97     "django.contrib.messages.context_processors.messages",
98     "benefits.core.context_processors.analytics",
99     "benefits.core.context_processors.recaptcha",
100 ]
101 
102 if DEBUG:
103     template_ctx_processors.extend(
104         [
105             "django.template.context_processors.debug",
106             "benefits.core.context_processors.debug",
107         ]
108     )
109 
110 if ADMIN:
111     template_ctx_processors.extend(
112         [
113             "django.contrib.auth.context_processors.auth",
114             "django.contrib.messages.context_processors.messages",
115         ]
116     )
117 
118 TEMPLATES = [
119     {
120         "BACKEND": "django.template.backends.django.DjangoTemplates",
121         "DIRS": [os.path.join(BASE_DIR, "benefits", "templates")],
122         "APP_DIRS": True,
123         "OPTIONS": {
124             "context_processors": template_ctx_processors,
125         },
126     },
127 ]
128 
129 WSGI_APPLICATION = "benefits.wsgi.application"
130 
131 DATABASES = {
132     "default": {
133         "ENGINE": "django.db.backends.sqlite3",
134         "NAME": os.environ.get("DJANGO_DB", "django") + ".db",
135     }
136 }
137 
138 # Password validation
139 
140 AUTH_PASSWORD_VALIDATORS = []
141 
142 if ADMIN:
143     AUTH_PASSWORD_VALIDATORS.extend(
144         [
145             {
146                 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
147             },
148             {
149                 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
150             },
151             {
152                 "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
153             },
154             {
155                 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
156             },
157         ]
158     )
159 
160 # OAuth configuration
161 
162 OAUTH_CLIENT_NAME = os.environ.get("DJANGO_OAUTH_CLIENT_NAME")
163 
164 if OAUTH_CLIENT_NAME:
165     AUTHLIB_OAUTH_CLIENTS = {
166         OAUTH_CLIENT_NAME: {
167             "client_id": os.environ.get("DJANGO_OAUTH_CLIENT_ID"),
168             "server_metadata_url": f"{os.environ.get('DJANGO_OAUTH_AUTHORITY')}/.well-known/openid-configuration",
169             "client_kwargs": {"code_challenge_method": "S256", "scope": os.environ.get("DJANGO_OAUTH_SCOPE")},
170         }
171     }
172 
173 # Internationalization
174 
175 LANGUAGE_CODE = "en"
176 
177 LANGUAGE_COOKIE_HTTPONLY = True
178 LANGUAGE_COOKIE_SAMESITE = "Strict"
179 LANGUAGE_COOKIE_SECURE = True
180 
181 LANGUAGES = [("en", "English"), ("es", "Español")]
182 
183 LOCALE_PATHS = [os.path.join(BASE_DIR, "benefits", "locale")]
184 
185 USE_I18N = True
186 USE_L10N = True
187 
188 TIME_ZONE = "UTC"
189 USE_TZ = True
190 
191 # Static files (CSS, JavaScript, Images)
192 
193 STATIC_URL = "/static/"
194 STATICFILES_DIRS = [os.path.join(BASE_DIR, "benefits", "static")]
195 STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
196 STATIC_ROOT = os.path.join(BASE_DIR, "static")
197 
198 # Logging configuration
199 
200 LOG_LEVEL = os.environ.get("DJANGO_LOG_LEVEL", "DEBUG" if DEBUG else "WARNING")
201 LOGGING = {
202     "version": 1,
203     "disable_existing_loggers": False,
204     "formatters": {
205         "default": {
206             "format": "[{asctime}] {levelname} {name}:{lineno} {message}",
207             "datefmt": "%d/%b/%Y %H:%M:%S",
208             "style": "{",
209         },
210     },
211     "handlers": {
212         "default": {"class": "logging.StreamHandler", "formatter": "default"},
213     },
214     "root": {
215         "handlers": ["default"],
216         "level": LOG_LEVEL,
217     },
218     "loggers": {"django": {"handlers": ["default"], "propagate": False}},
219 }
220 
221 # Analytics configuration
222 
223 ANALYTICS_KEY = os.environ.get("ANALYTICS_KEY")
224 
225 # Content Security Policy
226 # Configuration docs at https://django-csp.readthedocs.io/en/latest/configuration.html
227 
228 # In particular, note that the inner single-quotes are required!
229 # https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings
230 
231 CSP_DEFAULT_SRC = ["'self'"]
232 
233 env_connect_src = _filter_empty(os.environ.get("DJANGO_CSP_CONNECT_SRC", "").split(","))
234 CSP_CONNECT_SRC = ["'self'"]
235 CSP_CONNECT_SRC.extend(env_connect_src)
236 
237 env_font_src = _filter_empty(os.environ.get("DJANGO_CSP_FONT_SRC", "").split(","))
238 CSP_FONT_SRC = list(env_font_src)
239 
240 CSP_FRAME_ANCESTORS = ["'none'"]
241 CSP_FRAME_SRC = ["'none'"]
242 env_frame_src = _filter_empty(os.environ.get("DJANGO_CSP_FRAME_SRC", "").split(","))
243 if any(env_frame_src):
244     CSP_FRAME_SRC = list(env_frame_src)
245 
246 env_script_src = _filter_empty(os.environ.get("DJANGO_CSP_SCRIPT_SRC", "").split(","))
247 CSP_SCRIPT_SRC = ["'unsafe-inline'"]
248 CSP_SCRIPT_SRC.extend(env_script_src)
249 
250 env_style_src = _filter_empty(os.environ.get("DJANGO_CSP_STYLE_SRC", "").split(","))
251 CSP_STYLE_SRC = ["'self'", "'unsafe-inline'"]
252 CSP_STYLE_SRC.extend(env_style_src)
253 
254 # rate limit configuration
255 
256 # number of requests allowed in the given period
257 RATE_LIMIT = int(os.environ.get("DJANGO_RATE_LIMIT", 0))
258 
259 # HTTP request methods to rate limit
260 RATE_LIMIT_METHODS = os.environ.get("DJANGO_RATE_LIMIT_METHODS", "").upper().split(",")
261 
262 # number of seconds before additional requests are denied
263 RATE_LIMIT_PERIOD = int(os.environ.get("DJANGO_RATE_LIMIT_PERIOD", 0))
264 
265 # reCAPTCHA configuration
266 
267 RECAPTCHA_API_URL = os.environ.get("DJANGO_RECAPTCHA_API_URL")
268 RECAPTCHA_SITE_KEY = os.environ.get("DJANGO_RECAPTCHA_SITE_KEY")
269 RECAPTCHA_SECRET_KEY = os.environ.get("DJANGO_RECAPTCHA_SECRET_KEY")
270 RECAPTCHA_VERIFY_URL = os.environ.get("DJANGO_RECAPTCHA_VERIFY_URL")
271 RECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))
272
```
--- 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
@@ -90,6 +90,14 @@
 
 SECURE_BROWSER_XSS_FILTER = True
 
+# the NGINX reverse proxy sits in front of the application in deployed environments
+# SSL terminates before getting to Django, and NGINX adds this header to indicate
+# if the original request was secure or not
+#
+# See https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header
+if not DEBUG:
+    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
+
 ROOT_URLCONF = "benefits.urls"
 
 template_ctx_processors = [

{"golden_diff": "diff --git a/benefits/settings.py b/benefits/settings.py\n--- a/benefits/settings.py\n+++ b/benefits/settings.py\n@@ -90,6 +90,14 @@\n \n SECURE_BROWSER_XSS_FILTER = True\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/3.2/ref/settings/#secure-proxy-ssl-header\n+if not DEBUG:\n+    SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n+\n ROOT_URLCONF = \"benefits.urls\"\n \n template_ctx_processors = [\n", "issue": "Ensure OAuth redirect URLs are secure\nWe construct the OAUth redirect URLs using the Django [`reverse()`](https://docs.djangoproject.com/en/3.2/ref/urlresolvers/#reverse) helper and [`request.build_absolute_uri()`](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri).\r\n\r\nThis is done in e.g. the [`login()` view](https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py):\r\n\r\n```python\r\nROUTE_AUTH = \"oauth:authorize\"\r\n\r\nroute = reverse(ROUTE_AUTH)\r\n\r\nredirect_uri = request.build_absolute_uri(route)\r\n```\r\n\r\nThe docs for `build_absolute_uri` say:\r\n\r\n> Mixing HTTP and HTTPS on the same site is discouraged, therefore [build_absolute_uri()](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri) will always generate an absolute URI with the same scheme the current request has. If you need to redirect users to HTTPS, it\u2019s best to let your Web server redirect all HTTP traffic to HTTPS.\r\n\r\nWhen running in AWS, we have the Load Balancer sitting in front of all requests; this is the layer where HTTP --> HTTPS redirection happens and where the cert that the browser sees is installed.\r\n\r\nThen the request gets to the application container, where it first hits `nginx`. This is where requests for static files end; application requests are forwarded through to `gunicorn` over a Unix socket. Strictly speaking, Django does not receive an HTTPS request.\r\n\r\n## Potential solution\r\n\r\nThe [`SECURE_PROXY_SSL_HEADER`](https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header) Django setting seems to be aimed at helping this situation, where `nginx` sets a header with an indicated value on the request forwarded into the application via gunicorn, to tell the app if the request was originally secure or not.\r\n\r\nIn the app's [`nginx.conf`](https://github.com/cal-itp/benefits/blob/dev/nginx.conf#L55) we do set that header on forwarded requests.\r\n\r\n## Todo\r\n\r\n* [x] Ensure the current `nginx.conf` setting overwrites any header value coming in with the request and/or clear that header first\r\n* [x] Add the `SECURE_PROXY_SSL_HEADER` to `settings.py` when `DEBUG = False`\n", "before_files": [{"content": "\"\"\"\nDjango settings for benefits project.\n\"\"\"\nimport os\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[\"DJANGO_SECRET_KEY\"]\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[\"DJANGO_ALLOWED_HOSTS\"].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.extend([\"benefits.core.middleware.DebugSession\"])\n\nCSRF_COOKIE_AGE = None\nCSRF_COOKIE_SAMESITE = \"Strict\"\nCSRF_COOKIE_HTTPONLY = True\nCSRF_TRUSTED_ORIGINS = _filter_empty(os.environ[\"DJANGO_TRUSTED_ORIGINS\"].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\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.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\": os.environ.get(\"DJANGO_DB\", \"django\") + \".db\",\n    }\n}\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# OAuth configuration\n\nOAUTH_CLIENT_NAME = os.environ.get(\"DJANGO_OAUTH_CLIENT_NAME\")\n\nif OAUTH_CLIENT_NAME:\n    AUTHLIB_OAUTH_CLIENTS = {\n        OAUTH_CLIENT_NAME: {\n            \"client_id\": os.environ.get(\"DJANGO_OAUTH_CLIENT_ID\"),\n            \"server_metadata_url\": f\"{os.environ.get('DJANGO_OAUTH_AUTHORITY')}/.well-known/openid-configuration\",\n            \"client_kwargs\": {\"code_challenge_method\": \"S256\", \"scope\": os.environ.get(\"DJANGO_OAUTH_SCOPE\")},\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\")]\nSTATICFILES_STORAGE = \"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\"\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\n# Logging configuration\n\nLOG_LEVEL = os.environ.get(\"DJANGO_LOG_LEVEL\", \"DEBUG\" if DEBUG else \"WARNING\")\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"default\": {\n            \"format\": \"[{asctime}] {levelname} {name}:{lineno} {message}\",\n            \"datefmt\": \"%d/%b/%Y %H:%M:%S\",\n            \"style\": \"{\",\n        },\n    },\n    \"handlers\": {\n        \"default\": {\"class\": \"logging.StreamHandler\", \"formatter\": \"default\"},\n    },\n    \"root\": {\n        \"handlers\": [\"default\"],\n        \"level\": LOG_LEVEL,\n    },\n    \"loggers\": {\"django\": {\"handlers\": [\"default\"], \"propagate\": False}},\n}\n\n# Analytics configuration\n\nANALYTICS_KEY = os.environ.get(\"ANALYTICS_KEY\")\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\nenv_connect_src = _filter_empty(os.environ.get(\"DJANGO_CSP_CONNECT_SRC\", \"\").split(\",\"))\nCSP_CONNECT_SRC = [\"'self'\"]\nCSP_CONNECT_SRC.extend(env_connect_src)\n\nenv_font_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FONT_SRC\", \"\").split(\",\"))\nCSP_FONT_SRC = list(env_font_src)\n\nCSP_FRAME_ANCESTORS = [\"'none'\"]\nCSP_FRAME_SRC = [\"'none'\"]\nenv_frame_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FRAME_SRC\", \"\").split(\",\"))\nif any(env_frame_src):\n    CSP_FRAME_SRC = list(env_frame_src)\n\nenv_script_src = _filter_empty(os.environ.get(\"DJANGO_CSP_SCRIPT_SRC\", \"\").split(\",\"))\nCSP_SCRIPT_SRC = [\"'unsafe-inline'\"]\nCSP_SCRIPT_SRC.extend(env_script_src)\n\nenv_style_src = _filter_empty(os.environ.get(\"DJANGO_CSP_STYLE_SRC\", \"\").split(\",\"))\nCSP_STYLE_SRC = [\"'self'\", \"'unsafe-inline'\"]\nCSP_STYLE_SRC.extend(env_style_src)\n\n# rate limit configuration\n\n# number of requests allowed in the given period\nRATE_LIMIT = int(os.environ.get(\"DJANGO_RATE_LIMIT\", 0))\n\n# HTTP request methods to rate limit\nRATE_LIMIT_METHODS = os.environ.get(\"DJANGO_RATE_LIMIT_METHODS\", \"\").upper().split(\",\")\n\n# number of seconds before additional requests are denied\nRATE_LIMIT_PERIOD = int(os.environ.get(\"DJANGO_RATE_LIMIT_PERIOD\", 0))\n\n# reCAPTCHA configuration\n\nRECAPTCHA_API_URL = os.environ.get(\"DJANGO_RECAPTCHA_API_URL\")\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\")\nRECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))\n", "path": "benefits/settings.py"}], "after_files": [{"content": "\"\"\"\nDjango settings for benefits project.\n\"\"\"\nimport os\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[\"DJANGO_SECRET_KEY\"]\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[\"DJANGO_ALLOWED_HOSTS\"].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.extend([\"benefits.core.middleware.DebugSession\"])\n\nCSRF_COOKIE_AGE = None\nCSRF_COOKIE_SAMESITE = \"Strict\"\nCSRF_COOKIE_HTTPONLY = True\nCSRF_TRUSTED_ORIGINS = _filter_empty(os.environ[\"DJANGO_TRUSTED_ORIGINS\"].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# 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/3.2/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.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\": os.environ.get(\"DJANGO_DB\", \"django\") + \".db\",\n    }\n}\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# OAuth configuration\n\nOAUTH_CLIENT_NAME = os.environ.get(\"DJANGO_OAUTH_CLIENT_NAME\")\n\nif OAUTH_CLIENT_NAME:\n    AUTHLIB_OAUTH_CLIENTS = {\n        OAUTH_CLIENT_NAME: {\n            \"client_id\": os.environ.get(\"DJANGO_OAUTH_CLIENT_ID\"),\n            \"server_metadata_url\": f\"{os.environ.get('DJANGO_OAUTH_AUTHORITY')}/.well-known/openid-configuration\",\n            \"client_kwargs\": {\"code_challenge_method\": \"S256\", \"scope\": os.environ.get(\"DJANGO_OAUTH_SCOPE\")},\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\")]\nSTATICFILES_STORAGE = \"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\"\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\n# Logging configuration\n\nLOG_LEVEL = os.environ.get(\"DJANGO_LOG_LEVEL\", \"DEBUG\" if DEBUG else \"WARNING\")\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"default\": {\n            \"format\": \"[{asctime}] {levelname} {name}:{lineno} {message}\",\n            \"datefmt\": \"%d/%b/%Y %H:%M:%S\",\n            \"style\": \"{\",\n        },\n    },\n    \"handlers\": {\n        \"default\": {\"class\": \"logging.StreamHandler\", \"formatter\": \"default\"},\n    },\n    \"root\": {\n        \"handlers\": [\"default\"],\n        \"level\": LOG_LEVEL,\n    },\n    \"loggers\": {\"django\": {\"handlers\": [\"default\"], \"propagate\": False}},\n}\n\n# Analytics configuration\n\nANALYTICS_KEY = os.environ.get(\"ANALYTICS_KEY\")\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\nenv_connect_src = _filter_empty(os.environ.get(\"DJANGO_CSP_CONNECT_SRC\", \"\").split(\",\"))\nCSP_CONNECT_SRC = [\"'self'\"]\nCSP_CONNECT_SRC.extend(env_connect_src)\n\nenv_font_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FONT_SRC\", \"\").split(\",\"))\nCSP_FONT_SRC = list(env_font_src)\n\nCSP_FRAME_ANCESTORS = [\"'none'\"]\nCSP_FRAME_SRC = [\"'none'\"]\nenv_frame_src = _filter_empty(os.environ.get(\"DJANGO_CSP_FRAME_SRC\", \"\").split(\",\"))\nif any(env_frame_src):\n    CSP_FRAME_SRC = list(env_frame_src)\n\nenv_script_src = _filter_empty(os.environ.get(\"DJANGO_CSP_SCRIPT_SRC\", \"\").split(\",\"))\nCSP_SCRIPT_SRC = [\"'unsafe-inline'\"]\nCSP_SCRIPT_SRC.extend(env_script_src)\n\nenv_style_src = _filter_empty(os.environ.get(\"DJANGO_CSP_STYLE_SRC\", \"\").split(\",\"))\nCSP_STYLE_SRC = [\"'self'\", \"'unsafe-inline'\"]\nCSP_STYLE_SRC.extend(env_style_src)\n\n# rate limit configuration\n\n# number of requests allowed in the given period\nRATE_LIMIT = int(os.environ.get(\"DJANGO_RATE_LIMIT\", 0))\n\n# HTTP request methods to rate limit\nRATE_LIMIT_METHODS = os.environ.get(\"DJANGO_RATE_LIMIT_METHODS\", \"\").upper().split(\",\")\n\n# number of seconds before additional requests are denied\nRATE_LIMIT_PERIOD = int(os.environ.get(\"DJANGO_RATE_LIMIT_PERIOD\", 0))\n\n# reCAPTCHA configuration\n\nRECAPTCHA_API_URL = os.environ.get(\"DJANGO_RECAPTCHA_API_URL\")\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\")\nRECAPTCHA_ENABLED = all((RECAPTCHA_API_URL, RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY, RECAPTCHA_VERIFY_URL))\n", "path": "benefits/settings.py"}]}