From 148ab8138e116c4d813d1ca14155762f600f73f7 Mon Sep 17 00:00:00 2001 From: Anna Montare <267455234+annamontare-nava@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:29:26 -0600 Subject: [PATCH 1/6] enable CORS middleware --- docker-compose/web-env-vars.env | 2 +- hhs_oauth_server/settings/base_ec2.py | 47 +++++++++---------- hhs_oauth_server/settings/base_local.py | 47 +++++++++---------- .../external/prepare-environment-support.bash | 1 + 4 files changed, 46 insertions(+), 51 deletions(-) diff --git a/docker-compose/web-env-vars.env b/docker-compose/web-env-vars.env index c7e112e68..bbe08f068 100644 --- a/docker-compose/web-env-vars.env +++ b/docker-compose/web-env-vars.env @@ -21,6 +21,6 @@ FHIR_URL="https://prod-sbx.fhir.bfd.cmscloud.local" DJANGO_FHIR_CERTSTORE=/code/docker-compose/certstore/ # CORS configuration for local docker-based development # Allow all origins by default in local dev; override with environment if needed -CORS_ORIGIN_ALLOW_ALL=True +CORS_ORIGIN_ALLOW_ALL=False # Comma-separated list of allowed origins when CORS_ORIGIN_ALLOW_ALL is False # Example: CORS_ALLOWED_ORIGINS=https://localhost:3000, https://sandbox.bluebutton.cms.gov diff --git a/hhs_oauth_server/settings/base_ec2.py b/hhs_oauth_server/settings/base_ec2.py index a697e1879..bf6e9c9bc 100644 --- a/hhs_oauth_server/settings/base_ec2.py +++ b/hhs_oauth_server/settings/base_ec2.py @@ -291,31 +291,28 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') X_FRAME_OPTIONS = 'DENY' -CORS_ORIGIN_ALLOW_ALL = bool_env(env('CORS_ORIGIN_ALLOW_ALL', True)) -# TODO: Uncomment once Fargate migration is complete and we would have a better sense of what allowed origins would be. -# Once a Fargate post migration ticket is created, this should be addressed then. -# It is a possible security hardening that can be decided upon later. -# _CORS_ALLOWED = env("CORS_ALLOWED_ORIGINS", "") -# CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(",") if o.strip()] - -# # Restrict allowed HTTP methods and headers for CORS -# CORS_ALLOW_METHODS = [ -# "GET", -# "POST", -# "PUT", -# "PATCH", -# "DELETE", -# "OPTIONS", -# ] - -# CORS_ALLOW_HEADERS = ( -# "accept", -# "authorization", -# "content-type", -# "user-agent", -# "x-csrftoken", -# "x-requested-with", -# ) +CORS_ORIGIN_ALLOW_ALL = bool_env(env('CORS_ORIGIN_ALLOW_ALL', False)) +_CORS_ALLOWED = env('CORS_ALLOWED_ORIGINS', default='') +CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(',') if o.strip()] + +# Restrict allowed HTTP methods and headers for CORS +CORS_ALLOW_METHODS = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS', +] + +CORS_ALLOW_HEADERS = ( + 'accept', + 'authorization', + 'content-type', + 'user-agent', + 'x-csrftoken', + 'x-requested-with', +) ROOT_URLCONF = 'hhs_oauth_server.urls' diff --git a/hhs_oauth_server/settings/base_local.py b/hhs_oauth_server/settings/base_local.py index be9751d6c..3f6ad716a 100644 --- a/hhs_oauth_server/settings/base_local.py +++ b/hhs_oauth_server/settings/base_local.py @@ -422,31 +422,28 @@ # CORS settings # In local development we default to allowing all origins for convenience. -CORS_ORIGIN_ALLOW_ALL = env.bool('CORS_ORIGIN_ALLOW_ALL', default=True) -# TODO: Uncomment once Fargate migration is complete and we would have a better sense of what allowed origins would be. -# Once a Fargate post migration ticket is created, this should be addressed then. -# It is a possible security hardening that can be decided upon later. -# _CORS_ALLOWED = env("CORS_ALLOWED_ORIGINS", "") -# CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(",") if o.strip()] - -# # Restrict allowed HTTP methods and headers for CORS -# CORS_ALLOW_METHODS = [ -# "GET", -# "POST", -# "PUT", -# "PATCH", -# "DELETE", -# "OPTIONS", -# ] - -# CORS_ALLOW_HEADERS = ( -# "accept", -# "authorization", -# "content-type", -# "user-agent", -# "x-csrftoken", -# "x-requested-with", -# ) +CORS_ORIGIN_ALLOW_ALL = env.bool('CORS_ORIGIN_ALLOW_ALL', default=False) +_CORS_ALLOWED = env('CORS_ALLOWED_ORIGINS', default='') +CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(',') if o.strip()] + +# Restrict allowed HTTP methods and headers for CORS +CORS_ALLOW_METHODS = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS', +] + +CORS_ALLOW_HEADERS = ( + 'accept', + 'authorization', + 'content-type', + 'user-agent', + 'x-csrftoken', + 'x-requested-with', +) # Failed Login Attempt Module: AXES # Either integer or timedelta. diff --git a/ops/containers/bb-api/scripts/external/prepare-environment-support.bash b/ops/containers/bb-api/scripts/external/prepare-environment-support.bash index 7033de714..8cb627c6b 100755 --- a/ops/containers/bb-api/scripts/external/prepare-environment-support.bash +++ b/ops/containers/bb-api/scripts/external/prepare-environment-support.bash @@ -80,6 +80,7 @@ load_env_vars () { export BB2_SERVER_STD2FILE='' export BB20_ENABLE_REMOTE_DEBUG="${BB20_ENABLE_REMOTE_DEBUG:-true}" export BB20_REMOTE_DEBUG_WAIT_ATTACH="${BB20_REMOTE_DEBUG_WAIT_ATTACH:-false}" + export CORS_ALLOWED_ORIGINS="${CORS_ALLOWED_ORIGINS:-}" export DATABASES_CUSTOM="${DATABASES_CUSTOM:-postgres://postgres:toor@db:5432/bluebutton}" export DJANGO_FHIR_CERTSTORE="${DJANGO_FHIR_CERTSTORE:-/tmp/certstore}" export DJANGO_LOG_JSON_FORMAT_PRETTY="${DJANGO_LOG_JSON_FORMAT_PRETTY:-true}" From b7fba86655dbdc7a2cdcada7715b061dfffb0e03 Mon Sep 17 00:00:00 2001 From: Anna Montare <267455234+annamontare-nava@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:11:25 -0600 Subject: [PATCH 2/6] update sops values --- ops/services/01-config/values/prod.sopsw.yaml | 2 +- ops/services/01-config/values/sandbox.sopsw.yaml | 2 +- ops/services/01-config/values/test.sopsw.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ops/services/01-config/values/prod.sopsw.yaml b/ops/services/01-config/values/prod.sopsw.yaml index 89dcfa313..0b1317de9 100644 --- a/ops/services/01-config/values/prod.sopsw.yaml +++ b/ops/services/01-config/values/prod.sopsw.yaml @@ -16,7 +16,7 @@ /bb/prod/app/nonsensitive/target_env: prod /bb/prod/app/nonsensitive/django_application_title: Blue Button API /bb/prod/app/nonsensitive/signup_timeout_days: "7" -/bb/prod/app/nonsensitive/cors_origin_allow_all: "True" +/bb/prod/app/nonsensitive/cors_origin_allow_all: "False" #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] #ENC[AES256_GCM,data:dI8KK9uBMTe93U/s,iv:Y8grfxtOYQCgPlclkmaAbcHA3oKSLY1PutJivUU7Lks=,tag:FeEUBDFEfyWGhivSyw6iAQ==,type:comment] #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] diff --git a/ops/services/01-config/values/sandbox.sopsw.yaml b/ops/services/01-config/values/sandbox.sopsw.yaml index b775818e6..519d9d732 100644 --- a/ops/services/01-config/values/sandbox.sopsw.yaml +++ b/ops/services/01-config/values/sandbox.sopsw.yaml @@ -13,7 +13,7 @@ /bb/sandbox/app/nonsensitive/target_env: impl /bb/sandbox/app/nonsensitive/django_application_title: Blue Button API Sandbox /bb/sandbox/app/nonsensitive/signup_timeout_days: "7" -/bb/sandbox/app/nonsensitive/cors_origin_allow_all: "True" +/bb/sandbox/app/nonsensitive/cors_origin_allow_all: "False" #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] #ENC[AES256_GCM,data:9xyjWpwegeETsHi5d5brcIZ93Kwm,iv:rL3iOjQWeoK02FEtxmvk8M6Zm5bB+hrjDvtVR+0h1FA=,tag:U+NbU+F0if2Rs/c+qBnK5w==,type:comment] #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] diff --git a/ops/services/01-config/values/test.sopsw.yaml b/ops/services/01-config/values/test.sopsw.yaml index 700c88f92..a02fce6c7 100644 --- a/ops/services/01-config/values/test.sopsw.yaml +++ b/ops/services/01-config/values/test.sopsw.yaml @@ -15,7 +15,7 @@ /bb/test/app/nonsensitive/target_env: test /bb/test/app/nonsensitive/django_application_title: Blue Button API [TEST] /bb/test/app/nonsensitive/signup_timeout_days: "7" -/bb/test/app/nonsensitive/cors_origin_allow_all: "True" +/bb/test/app/nonsensitive/cors_origin_allow_all: "False" #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] #ENC[AES256_GCM,data:spJgnncvZi3qyraZgJC82sJghYiA,iv:ipn+nvndjFEoFw51PQTStyz5oplo1ULQXExWbsKidCQ=,tag:bthqEYK9+fJk5XC5vz0iAA==,type:comment] #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] From f090fcdca46598fdd6e591c5770746662aa03cc0 Mon Sep 17 00:00:00 2001 From: sb-benohe Date: Tue, 6 Oct 2026 13:14:04 -0400 Subject: [PATCH 3/6] LLM Assisted CORS Policy --- apps/core/apps.py | 3 ++ apps/core/signals.py | 11 +++++++ hhs_oauth_server/settings/base_ec2.py | 36 +++++++++++--------- hhs_oauth_server/tests.py | 47 ++++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 apps/core/signals.py diff --git a/apps/core/apps.py b/apps/core/apps.py index 14dc44c2f..b18af22ff 100644 --- a/apps/core/apps.py +++ b/apps/core/apps.py @@ -5,3 +5,6 @@ class CoreConfig(AppConfig): name = 'apps.core' label = 'core' verbose_name = 'Application Core' + + def ready(self): + from . import signals # noqa diff --git a/apps/core/signals.py b/apps/core/signals.py new file mode 100644 index 000000000..3850643e4 --- /dev/null +++ b/apps/core/signals.py @@ -0,0 +1,11 @@ +import re + +from corsheaders.signals import check_request_enabled +from django.conf import settings +from django.dispatch import receiver + + +@receiver(check_request_enabled) +def allow_any_origin_on_api_paths(sender, request, **kwargs): + api_cors_urls_regex = getattr(settings, 'API_CORS_URLS_REGEX', None) + return bool(api_cors_urls_regex and re.match(api_cors_urls_regex, request.path_info)) diff --git a/hhs_oauth_server/settings/base_ec2.py b/hhs_oauth_server/settings/base_ec2.py index bf6e9c9bc..49850f89a 100644 --- a/hhs_oauth_server/settings/base_ec2.py +++ b/hhs_oauth_server/settings/base_ec2.py @@ -291,24 +291,31 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') X_FRAME_OPTIONS = 'DENY' -CORS_ORIGIN_ALLOW_ALL = bool_env(env('CORS_ORIGIN_ALLOW_ALL', False)) -_CORS_ALLOWED = env('CORS_ALLOWED_ORIGINS', default='') -CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(',') if o.strip()] - -# Restrict allowed HTTP methods and headers for CORS -CORS_ALLOW_METHODS = [ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE', - 'OPTIONS', -] +# Move Admin to a variable url location +ADMIN_PREPEND_URL = env('DJANGO_ADMIN_PREPEND_URL', '') + +# Must stay False so admin paths are origin-restricted; API paths allow any origin via apps/core/signals.py. +CORS_ORIGIN_ALLOW_ALL = False + +API_CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' + +CORS_URLS_REGEX = ( + r'^/(' + ADMIN_PREPEND_URL + r'admin/' + r'|v[1-3]/accounts/' + r'|v[1-3]/o/(authorize|applications|authorized_tokens|tokens|expire_authenticated_user)/' + r'|docs/|health|creds)' +) +CORS_ALLOWED_ORIGIN_REGEXES = [r'^https://([a-z0-9-]+\.)*(cms|medicare)\.gov$'] + +CORS_ALLOW_CREDENTIALS = False + +CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS'] CORS_ALLOW_HEADERS = ( 'accept', 'authorization', 'content-type', + 'prefer', # v3 strict query-parameter validation 'user-agent', 'x-csrftoken', 'x-requested-with', @@ -772,9 +779,6 @@ LOGOUT_REDIRECT_URL = '/' -# Move Admin to a variable url location -ADMIN_PREPEND_URL = env('DJANGO_ADMIN_PREPEND_URL', '') - ALLOW_END_USER_EXTERNAL_AUTH = 'B' EXTERNAL_AUTH_NAME = 'Medicare.gov' diff --git a/hhs_oauth_server/tests.py b/hhs_oauth_server/tests.py index ea3d9ca3a..d1e9b7f9d 100644 --- a/hhs_oauth_server/tests.py +++ b/hhs_oauth_server/tests.py @@ -8,7 +8,9 @@ import json -from django.test import TestCase +from corsheaders.middleware import CorsMiddleware +from django.http import HttpResponse +from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings from django.test.client import Client from django.urls import reverse @@ -310,3 +312,46 @@ def test_valid_smart_config_response(self): self.assertEqual(type(json.loads(response_content)), type({})) self.assertCountEqual(response_json['capabilities'], CAPABILITIES) self.assertCountEqual(response_json['scopes_supported'], SCOPES_SUPPORTED) + + +# Mirrors the CORS settings in hhs_oauth_server/settings/base_ec2.py +@override_settings( + CORS_ORIGIN_ALLOW_ALL=False, + API_CORS_URLS_REGEX=r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))', + CORS_URLS_REGEX=( + r'^/(admin/' + r'|v[1-3]/accounts/' + r'|v[1-3]/o/(authorize|applications|authorized_tokens|tokens|expire_authenticated_user)/' + r'|docs/|health|creds)' + ), + CORS_ALLOWED_ORIGIN_REGEXES=[r'^https://([a-z0-9-]+\.)*(cms|medicare)\.gov$'], + CORS_ALLOW_CREDENTIALS=False, +) +class CorsPolicyTestCase(SimpleTestCase): + def _allow_origin(self, path, origin): + request = RequestFactory().get(path, HTTP_ORIGIN=origin) + response = CorsMiddleware(lambda r: HttpResponse())(request) + return response.get('Access-Control-Allow-Origin') + + def test_api_paths_allow_any_origin(self): + for path in ('/v2/fhir/Patient/', '/v3/o/token/', '/v1/connect/userinfo', '/.well-known/openid-configuration'): + self.assertEqual(self._allow_origin(path, 'https://example.com'), 'https://example.com') + + def test_admin_paths_allow_cms_and_medicare_subdomains(self): + for origin in ('https://cms.gov', 'https://bluebutton.cms.gov', 'https://a.b.medicare.gov'): + for path in ('/admin/', '/admin/metrics/tokens', '/v2/accounts/login', '/v2/o/applications/'): + self.assertEqual(self._allow_origin(path, origin), origin) + + def test_admin_paths_reject_other_origins(self): + for origin in ( + 'https://example.com', + 'https://evilcms.gov', + 'https://cms.gov.example.com', + 'http://www.cms.gov', + 'https://www.medicare.gov:8443', + ): + self.assertIsNone(self._allow_origin('/admin/', origin)) + + def test_unlisted_paths_get_no_cors_headers(self): + for path in ('/', '/mymedicare/login', '/v2/o/'): + self.assertIsNone(self._allow_origin(path, 'https://www.cms.gov')) From 65a7b3866b0a7b0876fb016dfa986af9a36eb4b4 Mon Sep 17 00:00:00 2001 From: Anna Montare <267455234+annamontare-nava@users.noreply.github.com> Date: Mon, 19 Oct 2026 16:27:39 -0600 Subject: [PATCH 4/6] a draft of a simpler config --- apps/core/apps.py | 3 --- apps/core/signals.py | 11 -------- docker-compose/web-env-vars.env | 2 +- hhs_oauth_server/settings/base_ec2.py | 27 +++++++++---------- hhs_oauth_server/settings/base_local.py | 13 +++++---- hhs_oauth_server/tests.py | 18 +++---------- .../external/prepare-environment-support.bash | 1 - ops/services/01-config/values/prod.sopsw.yaml | 2 +- .../01-config/values/sandbox.sopsw.yaml | 2 +- ops/services/01-config/values/test.sopsw.yaml | 2 +- 10 files changed, 25 insertions(+), 56 deletions(-) delete mode 100644 apps/core/signals.py diff --git a/apps/core/apps.py b/apps/core/apps.py index b18af22ff..14dc44c2f 100644 --- a/apps/core/apps.py +++ b/apps/core/apps.py @@ -5,6 +5,3 @@ class CoreConfig(AppConfig): name = 'apps.core' label = 'core' verbose_name = 'Application Core' - - def ready(self): - from . import signals # noqa diff --git a/apps/core/signals.py b/apps/core/signals.py deleted file mode 100644 index 3850643e4..000000000 --- a/apps/core/signals.py +++ /dev/null @@ -1,11 +0,0 @@ -import re - -from corsheaders.signals import check_request_enabled -from django.conf import settings -from django.dispatch import receiver - - -@receiver(check_request_enabled) -def allow_any_origin_on_api_paths(sender, request, **kwargs): - api_cors_urls_regex = getattr(settings, 'API_CORS_URLS_REGEX', None) - return bool(api_cors_urls_regex and re.match(api_cors_urls_regex, request.path_info)) diff --git a/docker-compose/web-env-vars.env b/docker-compose/web-env-vars.env index bbe08f068..c7e112e68 100644 --- a/docker-compose/web-env-vars.env +++ b/docker-compose/web-env-vars.env @@ -21,6 +21,6 @@ FHIR_URL="https://prod-sbx.fhir.bfd.cmscloud.local" DJANGO_FHIR_CERTSTORE=/code/docker-compose/certstore/ # CORS configuration for local docker-based development # Allow all origins by default in local dev; override with environment if needed -CORS_ORIGIN_ALLOW_ALL=False +CORS_ORIGIN_ALLOW_ALL=True # Comma-separated list of allowed origins when CORS_ORIGIN_ALLOW_ALL is False # Example: CORS_ALLOWED_ORIGINS=https://localhost:3000, https://sandbox.bluebutton.cms.gov diff --git a/hhs_oauth_server/settings/base_ec2.py b/hhs_oauth_server/settings/base_ec2.py index 49850f89a..3aa77be94 100644 --- a/hhs_oauth_server/settings/base_ec2.py +++ b/hhs_oauth_server/settings/base_ec2.py @@ -291,25 +291,19 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') X_FRAME_OPTIONS = 'DENY' -# Move Admin to a variable url location -ADMIN_PREPEND_URL = env('DJANGO_ADMIN_PREPEND_URL', '') - -# Must stay False so admin paths are origin-restricted; API paths allow any origin via apps/core/signals.py. -CORS_ORIGIN_ALLOW_ALL = False +# CORS settings +CORS_ALLOW_ALL_ORIGINS = bool_env(env('CORS_ALLOW_ALL_ORIGINS', default=False)) -API_CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' - -CORS_URLS_REGEX = ( - r'^/(' + ADMIN_PREPEND_URL + r'admin/' - r'|v[1-3]/accounts/' - r'|v[1-3]/o/(authorize|applications|authorized_tokens|tokens|expire_authenticated_user)/' - r'|docs/|health|creds)' -) -CORS_ALLOWED_ORIGIN_REGEXES = [r'^https://([a-z0-9-]+\.)*(cms|medicare)\.gov$'] +CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' CORS_ALLOW_CREDENTIALS = False -CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS'] +# Restrict allowed HTTP methods and headers for CORS +CORS_ALLOW_METHODS = [ + 'GET', + 'POST', + 'OPTIONS', +] CORS_ALLOW_HEADERS = ( 'accept', @@ -779,6 +773,9 @@ LOGOUT_REDIRECT_URL = '/' +# Move Admin to a variable url location +ADMIN_PREPEND_URL = env('DJANGO_ADMIN_PREPEND_URL', '') + ALLOW_END_USER_EXTERNAL_AUTH = 'B' EXTERNAL_AUTH_NAME = 'Medicare.gov' diff --git a/hhs_oauth_server/settings/base_local.py b/hhs_oauth_server/settings/base_local.py index 3f6ad716a..e414e0f6e 100644 --- a/hhs_oauth_server/settings/base_local.py +++ b/hhs_oauth_server/settings/base_local.py @@ -421,18 +421,16 @@ } # CORS settings -# In local development we default to allowing all origins for convenience. -CORS_ORIGIN_ALLOW_ALL = env.bool('CORS_ORIGIN_ALLOW_ALL', default=False) -_CORS_ALLOWED = env('CORS_ALLOWED_ORIGINS', default='') -CORS_ALLOWED_ORIGINS = [o.strip() for o in _CORS_ALLOWED.split(',') if o.strip()] +CORS_ALLOW_ALL_ORIGINS = env.bool('CORS_ALLOW_ALL_ORIGINS', default=False) + +CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' + +CORS_ALLOW_CREDENTIALS = False # Restrict allowed HTTP methods and headers for CORS CORS_ALLOW_METHODS = [ 'GET', 'POST', - 'PUT', - 'PATCH', - 'DELETE', 'OPTIONS', ] @@ -440,6 +438,7 @@ 'accept', 'authorization', 'content-type', + 'prefer', # v3 strict query-parameter validation 'user-agent', 'x-csrftoken', 'x-requested-with', diff --git a/hhs_oauth_server/tests.py b/hhs_oauth_server/tests.py index d1e9b7f9d..463067c8e 100644 --- a/hhs_oauth_server/tests.py +++ b/hhs_oauth_server/tests.py @@ -316,15 +316,8 @@ def test_valid_smart_config_response(self): # Mirrors the CORS settings in hhs_oauth_server/settings/base_ec2.py @override_settings( - CORS_ORIGIN_ALLOW_ALL=False, - API_CORS_URLS_REGEX=r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))', - CORS_URLS_REGEX=( - r'^/(admin/' - r'|v[1-3]/accounts/' - r'|v[1-3]/o/(authorize|applications|authorized_tokens|tokens|expire_authenticated_user)/' - r'|docs/|health|creds)' - ), - CORS_ALLOWED_ORIGIN_REGEXES=[r'^https://([a-z0-9-]+\.)*(cms|medicare)\.gov$'], + CORS_ALLOW_ALL_ORIGINS=True, + CORS_URLS_REGEX=r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))', CORS_ALLOW_CREDENTIALS=False, ) class CorsPolicyTestCase(SimpleTestCase): @@ -335,12 +328,7 @@ def _allow_origin(self, path, origin): def test_api_paths_allow_any_origin(self): for path in ('/v2/fhir/Patient/', '/v3/o/token/', '/v1/connect/userinfo', '/.well-known/openid-configuration'): - self.assertEqual(self._allow_origin(path, 'https://example.com'), 'https://example.com') - - def test_admin_paths_allow_cms_and_medicare_subdomains(self): - for origin in ('https://cms.gov', 'https://bluebutton.cms.gov', 'https://a.b.medicare.gov'): - for path in ('/admin/', '/admin/metrics/tokens', '/v2/accounts/login', '/v2/o/applications/'): - self.assertEqual(self._allow_origin(path, origin), origin) + self.assertEqual(self._allow_origin(path, 'https://example.com'), '*') def test_admin_paths_reject_other_origins(self): for origin in ( diff --git a/ops/containers/bb-api/scripts/external/prepare-environment-support.bash b/ops/containers/bb-api/scripts/external/prepare-environment-support.bash index 30d756486..273047777 100755 --- a/ops/containers/bb-api/scripts/external/prepare-environment-support.bash +++ b/ops/containers/bb-api/scripts/external/prepare-environment-support.bash @@ -80,7 +80,6 @@ load_env_vars () { export BB2_SERVER_STD2FILE='' export BB20_ENABLE_REMOTE_DEBUG="${BB20_ENABLE_REMOTE_DEBUG:-true}" export BB20_REMOTE_DEBUG_WAIT_ATTACH="${BB20_REMOTE_DEBUG_WAIT_ATTACH:-false}" - export CORS_ALLOWED_ORIGINS="${CORS_ALLOWED_ORIGINS:-}" export DATABASES_CUSTOM="${DATABASES_CUSTOM:-postgres://postgres:toor@db:5432/bluebutton}" export DJANGO_FHIR_CERTSTORE="${DJANGO_FHIR_CERTSTORE:-/tmp/certstore}" export DJANGO_LOG_JSON_FORMAT_PRETTY="${DJANGO_LOG_JSON_FORMAT_PRETTY:-true}" diff --git a/ops/services/01-config/values/prod.sopsw.yaml b/ops/services/01-config/values/prod.sopsw.yaml index 0b1317de9..a459e7729 100644 --- a/ops/services/01-config/values/prod.sopsw.yaml +++ b/ops/services/01-config/values/prod.sopsw.yaml @@ -16,7 +16,7 @@ /bb/prod/app/nonsensitive/target_env: prod /bb/prod/app/nonsensitive/django_application_title: Blue Button API /bb/prod/app/nonsensitive/signup_timeout_days: "7" -/bb/prod/app/nonsensitive/cors_origin_allow_all: "False" +/bb/prod/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] #ENC[AES256_GCM,data:dI8KK9uBMTe93U/s,iv:Y8grfxtOYQCgPlclkmaAbcHA3oKSLY1PutJivUU7Lks=,tag:FeEUBDFEfyWGhivSyw6iAQ==,type:comment] #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] diff --git a/ops/services/01-config/values/sandbox.sopsw.yaml b/ops/services/01-config/values/sandbox.sopsw.yaml index 519d9d732..7a7d1c1c4 100644 --- a/ops/services/01-config/values/sandbox.sopsw.yaml +++ b/ops/services/01-config/values/sandbox.sopsw.yaml @@ -13,7 +13,7 @@ /bb/sandbox/app/nonsensitive/target_env: impl /bb/sandbox/app/nonsensitive/django_application_title: Blue Button API Sandbox /bb/sandbox/app/nonsensitive/signup_timeout_days: "7" -/bb/sandbox/app/nonsensitive/cors_origin_allow_all: "False" +/bb/sandbox/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] #ENC[AES256_GCM,data:9xyjWpwegeETsHi5d5brcIZ93Kwm,iv:rL3iOjQWeoK02FEtxmvk8M6Zm5bB+hrjDvtVR+0h1FA=,tag:U+NbU+F0if2Rs/c+qBnK5w==,type:comment] #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] diff --git a/ops/services/01-config/values/test.sopsw.yaml b/ops/services/01-config/values/test.sopsw.yaml index a02fce6c7..2f48b0c76 100644 --- a/ops/services/01-config/values/test.sopsw.yaml +++ b/ops/services/01-config/values/test.sopsw.yaml @@ -15,7 +15,7 @@ /bb/test/app/nonsensitive/target_env: test /bb/test/app/nonsensitive/django_application_title: Blue Button API [TEST] /bb/test/app/nonsensitive/signup_timeout_days: "7" -/bb/test/app/nonsensitive/cors_origin_allow_all: "False" +/bb/test/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] #ENC[AES256_GCM,data:spJgnncvZi3qyraZgJC82sJghYiA,iv:ipn+nvndjFEoFw51PQTStyz5oplo1ULQXExWbsKidCQ=,tag:bthqEYK9+fJk5XC5vz0iAA==,type:comment] #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] From 154e3a3bfc220848bef128722aa4f9c737d53a67 Mon Sep 17 00:00:00 2001 From: Anna Montare <267455234+annamontare-nava@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:14:20 -0600 Subject: [PATCH 5/6] set default to true since that is what is being used --- hhs_oauth_server/settings/base_ec2.py | 2 +- hhs_oauth_server/settings/base_local.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hhs_oauth_server/settings/base_ec2.py b/hhs_oauth_server/settings/base_ec2.py index 3aa77be94..18ccad749 100644 --- a/hhs_oauth_server/settings/base_ec2.py +++ b/hhs_oauth_server/settings/base_ec2.py @@ -292,7 +292,7 @@ X_FRAME_OPTIONS = 'DENY' # CORS settings -CORS_ALLOW_ALL_ORIGINS = bool_env(env('CORS_ALLOW_ALL_ORIGINS', default=False)) +CORS_ALLOW_ALL_ORIGINS = bool_env(env('CORS_ALLOW_ALL_ORIGINS', default=True)) CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' diff --git a/hhs_oauth_server/settings/base_local.py b/hhs_oauth_server/settings/base_local.py index e414e0f6e..42811e039 100644 --- a/hhs_oauth_server/settings/base_local.py +++ b/hhs_oauth_server/settings/base_local.py @@ -421,7 +421,7 @@ } # CORS settings -CORS_ALLOW_ALL_ORIGINS = env.bool('CORS_ALLOW_ALL_ORIGINS', default=False) +CORS_ALLOW_ALL_ORIGINS = env.bool('CORS_ALLOW_ALL_ORIGINS', default=True) CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' From a4c94935f79b8292b4e48020a1b27abf9d0e3448 Mon Sep 17 00:00:00 2001 From: sb-benohe Date: Tue, 20 Oct 2026 16:07:06 -0400 Subject: [PATCH 6/6] Remove CORS --- .../tests/fhir_resources/fhir_meta_v1.json | 2 +- .../tests/fhir_resources/fhir_meta_v2.json | 2 +- apps/fhir/bluebutton/tests/test_utils.py | 8 +-- apps/fhir/bluebutton/utils.py | 4 +- docker-compose/web-env-vars.env | 5 -- hhs_oauth_server/settings/base_ec2.py | 26 --------- hhs_oauth_server/settings/base_local.py | 26 --------- hhs_oauth_server/tests.py | 53 ++++++++----------- ops/services/01-config/values/prod.sopsw.yaml | 1 - .../01-config/values/sandbox.sopsw.yaml | 1 - ops/services/01-config/values/test.sopsw.yaml | 1 - requirements/requirements.dev.txt | 5 -- requirements/requirements.in | 1 - requirements/requirements.txt | 5 -- static/api-docs/connectathon_openapi.yaml | 6 +-- static/api-docs/openapi.yaml | 6 +-- 16 files changed, 35 insertions(+), 117 deletions(-) diff --git a/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v1.json b/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v1.json index d9731bf2c..11e34de4c 100755 --- a/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v1.json +++ b/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v1.json @@ -142,7 +142,7 @@ } ], "security": { - "cors": true, + "cors": false, "service": [ { "text": "OAuth", diff --git a/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v2.json b/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v2.json index 82f863476..10fd66fdc 100755 --- a/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v2.json +++ b/apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v2.json @@ -135,7 +135,7 @@ } ], "security": { - "cors": true, + "cors": false, "service": [ { "text": "OAuth", diff --git a/apps/fhir/bluebutton/tests/test_utils.py b/apps/fhir/bluebutton/tests/test_utils.py index 25546ddc3..3cc7cf346 100644 --- a/apps/fhir/bluebutton/tests/test_utils.py +++ b/apps/fhir/bluebutton/tests/test_utils.py @@ -415,7 +415,7 @@ def test_oauth_resource_empty(self): result = build_oauth_resource(request, version=Versions.V1) - expected = True + expected = False self.assertEqual(result['cors'], expected) @@ -427,7 +427,7 @@ def test_oauth_resource_json(self): result = build_oauth_resource(request, Versions.V1, 'json') - expected = True + expected = False self.assertEqual(result['cors'], expected) @@ -439,9 +439,9 @@ def test_oauth_resource_xml(self): result = build_oauth_resource(request, Versions.V1, 'xml') - expected = 'true' + expected = 'false' - self.assertEqual(result[16:33], expected) + self.assertIn(expected, result) class PatientMatchResponseJsonTestCase(BaseApiTest): diff --git a/apps/fhir/bluebutton/utils.py b/apps/fhir/bluebutton/utils.py index 38c97ad4c..e9620728d 100644 --- a/apps/fhir/bluebutton/utils.py +++ b/apps/fhir/bluebutton/utils.py @@ -613,7 +613,7 @@ def build_oauth_resource(request, version=Versions.NOT_AN_API_VERSION, format_ty if format_type.lower() == 'xml': security = """ - true + false OAuth @@ -652,7 +652,7 @@ def build_oauth_resource(request, version=Versions.NOT_AN_API_VERSION, format_ty else: # json security = {} - security['cors'] = True + security['cors'] = False security['service'] = [ { 'text': 'OAuth', diff --git a/docker-compose/web-env-vars.env b/docker-compose/web-env-vars.env index c7e112e68..d1a984d7c 100644 --- a/docker-compose/web-env-vars.env +++ b/docker-compose/web-env-vars.env @@ -19,8 +19,3 @@ DJANGO_USER_ID_SALT=${DJANGO_USER_ID_SALT} DJANGO_USER_ID_ITERATIONS=${DJANGO_USER_ID_ITERATIONS} FHIR_URL="https://prod-sbx.fhir.bfd.cmscloud.local" DJANGO_FHIR_CERTSTORE=/code/docker-compose/certstore/ -# CORS configuration for local docker-based development -# Allow all origins by default in local dev; override with environment if needed -CORS_ORIGIN_ALLOW_ALL=True -# Comma-separated list of allowed origins when CORS_ORIGIN_ALLOW_ALL is False -# Example: CORS_ALLOWED_ORIGINS=https://localhost:3000, https://sandbox.bluebutton.cms.gov diff --git a/hhs_oauth_server/settings/base_ec2.py b/hhs_oauth_server/settings/base_ec2.py index 18ccad749..59459d8b7 100644 --- a/hhs_oauth_server/settings/base_ec2.py +++ b/hhs_oauth_server/settings/base_ec2.py @@ -206,7 +206,6 @@ 'apps.authorization', 'apps.bb2_tools', # 3rd Party --------------------- - 'corsheaders', 'django_bootstrap5', 'waffle', # dot (django-oauth-toolkit) must be installed after apps.dot_ext in order to override templates @@ -261,7 +260,6 @@ 'hhs_oauth_server.middleware.SecurityHeadersMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'hhs_oauth_server.request_logging.RequestTimeLoggingMiddleware', - 'corsheaders.middleware.CorsMiddleware', # Must be before CommonMiddleware but after SessionMiddleware 'django.middleware.locale.LocaleMiddleware', # Middleware that can send a response must be below this line @@ -291,30 +289,6 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') X_FRAME_OPTIONS = 'DENY' -# CORS settings -CORS_ALLOW_ALL_ORIGINS = bool_env(env('CORS_ALLOW_ALL_ORIGINS', default=True)) - -CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' - -CORS_ALLOW_CREDENTIALS = False - -# Restrict allowed HTTP methods and headers for CORS -CORS_ALLOW_METHODS = [ - 'GET', - 'POST', - 'OPTIONS', -] - -CORS_ALLOW_HEADERS = ( - 'accept', - 'authorization', - 'content-type', - 'prefer', # v3 strict query-parameter validation - 'user-agent', - 'x-csrftoken', - 'x-requested-with', -) - ROOT_URLCONF = 'hhs_oauth_server.urls' TEMPLATES = [ diff --git a/hhs_oauth_server/settings/base_local.py b/hhs_oauth_server/settings/base_local.py index 42811e039..fccc46cdf 100644 --- a/hhs_oauth_server/settings/base_local.py +++ b/hhs_oauth_server/settings/base_local.py @@ -68,7 +68,6 @@ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'hhs_oauth_server.request_logging.RequestTimeLoggingMiddleware', - 'corsheaders.middleware.CorsMiddleware', # Must be before CommonMiddleware but after SessionMiddleware 'django.middleware.locale.LocaleMiddleware', # Middleware that can send a response must be below this line @@ -150,7 +149,6 @@ 'apps.authorization', 'apps.bb2_tools', # 3rd Party --------------------- - 'corsheaders', 'django_bootstrap5', 'waffle', # DOT must be installed after apps.dot_ext in order to override templates @@ -420,30 +418,6 @@ }, } -# CORS settings -CORS_ALLOW_ALL_ORIGINS = env.bool('CORS_ALLOW_ALL_ORIGINS', default=True) - -CORS_URLS_REGEX = r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))' - -CORS_ALLOW_CREDENTIALS = False - -# Restrict allowed HTTP methods and headers for CORS -CORS_ALLOW_METHODS = [ - 'GET', - 'POST', - 'OPTIONS', -] - -CORS_ALLOW_HEADERS = ( - 'accept', - 'authorization', - 'content-type', - 'prefer', # v3 strict query-parameter validation - 'user-agent', - 'x-csrftoken', - 'x-requested-with', -) - # Failed Login Attempt Module: AXES # Either integer or timedelta. # If integer interpreted, as hours diff --git a/hhs_oauth_server/tests.py b/hhs_oauth_server/tests.py index 463067c8e..b1f77d7df 100644 --- a/hhs_oauth_server/tests.py +++ b/hhs_oauth_server/tests.py @@ -8,9 +8,8 @@ import json -from corsheaders.middleware import CorsMiddleware -from django.http import HttpResponse -from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings +from django.conf import settings +from django.test import TestCase from django.test.client import Client from django.urls import reverse @@ -314,32 +313,22 @@ def test_valid_smart_config_response(self): self.assertCountEqual(response_json['scopes_supported'], SCOPES_SUPPORTED) -# Mirrors the CORS settings in hhs_oauth_server/settings/base_ec2.py -@override_settings( - CORS_ALLOW_ALL_ORIGINS=True, - CORS_URLS_REGEX=r'^/(\.well-known/|v[1-3]/(fhir/|connect/|o/(token|revoke_token|revoke|introspect)/?$))', - CORS_ALLOW_CREDENTIALS=False, -) -class CorsPolicyTestCase(SimpleTestCase): - def _allow_origin(self, path, origin): - request = RequestFactory().get(path, HTTP_ORIGIN=origin) - response = CorsMiddleware(lambda r: HttpResponse())(request) - return response.get('Access-Control-Allow-Origin') - - def test_api_paths_allow_any_origin(self): - for path in ('/v2/fhir/Patient/', '/v3/o/token/', '/v1/connect/userinfo', '/.well-known/openid-configuration'): - self.assertEqual(self._allow_origin(path, 'https://example.com'), '*') - - def test_admin_paths_reject_other_origins(self): - for origin in ( - 'https://example.com', - 'https://evilcms.gov', - 'https://cms.gov.example.com', - 'http://www.cms.gov', - 'https://www.medicare.gov:8443', - ): - self.assertIsNone(self._allow_origin('/admin/', origin)) - - def test_unlisted_paths_get_no_cors_headers(self): - for path in ('/', '/mymedicare/login', '/v2/o/'): - self.assertIsNone(self._allow_origin(path, 'https://www.cms.gov')) +class CorsPolicyTestCase(TestCase): + def test_cors_middleware_is_not_installed(self): + self.assertNotIn('corsheaders', settings.INSTALLED_APPS) + self.assertNotIn('corsheaders.middleware.CorsMiddleware', settings.MIDDLEWARE) + + def test_cross_origin_response_has_no_cors_headers(self): + response = self.client.get(reverse('openid-configuration'), HTTP_ORIGIN='https://example.com') + self.assertEqual(response.status_code, 200) + self.assertNotIn('Access-Control-Allow-Origin', response) + self.assertNotIn('Access-Control-Allow-Credentials', response) + + def test_preflight_does_not_allow_cross_origin_requests(self): + response = self.client.options( + reverse('openid-configuration'), + HTTP_ORIGIN='https://example.com', + HTTP_ACCESS_CONTROL_REQUEST_METHOD='GET', + ) + self.assertNotIn('Access-Control-Allow-Origin', response) + self.assertNotIn('Access-Control-Allow-Methods', response) diff --git a/ops/services/01-config/values/prod.sopsw.yaml b/ops/services/01-config/values/prod.sopsw.yaml index a459e7729..43cd2c094 100644 --- a/ops/services/01-config/values/prod.sopsw.yaml +++ b/ops/services/01-config/values/prod.sopsw.yaml @@ -16,7 +16,6 @@ /bb/prod/app/nonsensitive/target_env: prod /bb/prod/app/nonsensitive/django_application_title: Blue Button API /bb/prod/app/nonsensitive/signup_timeout_days: "7" -/bb/prod/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] #ENC[AES256_GCM,data:dI8KK9uBMTe93U/s,iv:Y8grfxtOYQCgPlclkmaAbcHA3oKSLY1PutJivUU7Lks=,tag:FeEUBDFEfyWGhivSyw6iAQ==,type:comment] #ENC[AES256_GCM,data:cjxYrqg/L58XyzZNVBl/xzq6OkIsxhtHe/gyDGvbSnUMuEmBybfd4e+JzGhG49ztVDIUP1btyokUy6mF5BOW53npkRsJrqJoPzLpGeQ=,iv:qaDWHiwa+TT+2/uBMtdSIm7EpbwgeTsMx8XUhBcDh48=,tag:FsvNuBIpoRdn1KWTaIM7iA==,type:comment] diff --git a/ops/services/01-config/values/sandbox.sopsw.yaml b/ops/services/01-config/values/sandbox.sopsw.yaml index 7a7d1c1c4..0756d4835 100644 --- a/ops/services/01-config/values/sandbox.sopsw.yaml +++ b/ops/services/01-config/values/sandbox.sopsw.yaml @@ -13,7 +13,6 @@ /bb/sandbox/app/nonsensitive/target_env: impl /bb/sandbox/app/nonsensitive/django_application_title: Blue Button API Sandbox /bb/sandbox/app/nonsensitive/signup_timeout_days: "7" -/bb/sandbox/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] #ENC[AES256_GCM,data:9xyjWpwegeETsHi5d5brcIZ93Kwm,iv:rL3iOjQWeoK02FEtxmvk8M6Zm5bB+hrjDvtVR+0h1FA=,tag:U+NbU+F0if2Rs/c+qBnK5w==,type:comment] #ENC[AES256_GCM,data:etJuMbKWQ0eVN2BYCoqNPOCysTgkaSkJlDAhs4q7QcdoMf8EN+xJ4RwNOf+xCRGcOZeHkzUJlCETeZ179TWzfB3N49R8p3LU2ZrSF54=,iv:ZwQ7c8FbeCF4jQKXALyxcK+WXwV5o6X2/QFGqAeo2tI=,tag:4Utwk/gsCx+eMXSo5L9SCw==,type:comment] diff --git a/ops/services/01-config/values/test.sopsw.yaml b/ops/services/01-config/values/test.sopsw.yaml index 2f48b0c76..9f567d0f6 100644 --- a/ops/services/01-config/values/test.sopsw.yaml +++ b/ops/services/01-config/values/test.sopsw.yaml @@ -15,7 +15,6 @@ /bb/test/app/nonsensitive/target_env: test /bb/test/app/nonsensitive/django_application_title: Blue Button API [TEST] /bb/test/app/nonsensitive/signup_timeout_days: "7" -/bb/test/app/nonsensitive/cors_allow_all_origins: "True" #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] #ENC[AES256_GCM,data:spJgnncvZi3qyraZgJC82sJghYiA,iv:ipn+nvndjFEoFw51PQTStyz5oplo1ULQXExWbsKidCQ=,tag:bthqEYK9+fJk5XC5vz0iAA==,type:comment] #ENC[AES256_GCM,data:NEJYqpcJLWvsdG8PYRiAeGCrFpURmucrKzWdyMaq6WEstOCPfJS6JfMTFhthqBzBu8CVXGDcQlvlnlXpNH1SjOGpTA7vsBFt2shvegI=,iv:H9c/gKlV7taXi3VeOGys7O2XBdZ/EQvd0NSBsY6I48g=,tag:qS6CR1nX3w+jUcGPLAyWgg==,type:comment] diff --git a/requirements/requirements.dev.txt b/requirements/requirements.dev.txt index 399d4e944..f0b23f5dd 100644 --- a/requirements/requirements.dev.txt +++ b/requirements/requirements.dev.txt @@ -600,7 +600,6 @@ django==6.0.8 \ # dj-database-url # django-axes # django-bootstrap5 - # django-cors-headers # django-filter # django-ipware # django-oauth-toolkit @@ -615,10 +614,6 @@ django-bootstrap5==25.3 \ --hash=sha256:3d381a5329a1439ac3237456c28cc8170c62b4c56ad3564d57876ffc3d4c5e78 \ --hash=sha256:cc36f1a19d3200c0f5628c703320edb5c4e39e19bf17607fadf58ddb11828912 # via -r requirements.in -django-cors-headers==4.0.0 \ - --hash=sha256:a971cd4c75b29974068cc36b5c595698822f1e0edd5f1b32ea42ea37326ad4aa \ - --hash=sha256:e3cbd247a1a835da4cf71a70d4214378813ea7e08337778b82cb2c1bc19d28d6 - # via -r requirements.in django-environ==0.12.0 \ --hash=sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a \ --hash=sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca diff --git a/requirements/requirements.in b/requirements/requirements.in index 826d20c88..c03347e0e 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -4,7 +4,6 @@ dj-database-url == 2.0.0 django == 6.0.8 django-axes == 5.41.1 django-bootstrap5 == 25.3 # used in our templates -django-cors-headers == 4.0.0 # used in our settings django-environ == 0.12.0 django-filter == 25.2 django-getenv == 1.3.2 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a38c672bf..3990bcd54 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -436,7 +436,6 @@ django==6.0.8 \ # dj-database-url # django-axes # django-bootstrap5 - # django-cors-headers # django-filter # django-ipware # django-oauth-toolkit @@ -451,10 +450,6 @@ django-bootstrap5==25.3 \ --hash=sha256:3d381a5329a1439ac3237456c28cc8170c62b4c56ad3564d57876ffc3d4c5e78 \ --hash=sha256:cc36f1a19d3200c0f5628c703320edb5c4e39e19bf17607fadf58ddb11828912 # via -r requirements.in -django-cors-headers==4.0.0 \ - --hash=sha256:a971cd4c75b29974068cc36b5c595698822f1e0edd5f1b32ea42ea37326ad4aa \ - --hash=sha256:e3cbd247a1a835da4cf71a70d4214378813ea7e08337778b82cb2c1bc19d28d6 - # via -r requirements.in django-environ==0.12.0 \ --hash=sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a \ --hash=sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca diff --git a/static/api-docs/connectathon_openapi.yaml b/static/api-docs/connectathon_openapi.yaml index 4793db375..027e097c5 100644 --- a/static/api-docs/connectathon_openapi.yaml +++ b/static/api-docs/connectathon_openapi.yaml @@ -1724,7 +1724,7 @@ components: - `cursor=200` the first record is the 201st record - `cursor=1000` the first record is the 1001st record security: - cors: true + cors: false service: - text: OAuth coding: @@ -2131,7 +2131,7 @@ components: - `cursor=200` the first record is the 201st record - `cursor=1000` the first record is the 1001st record security: - cors: true + cors: false service: - text: OAuth coding: @@ -2538,7 +2538,7 @@ components: - `cursor=200` the first record is the 201st record - `cursor=1000` the first record is the 1001st record security: - cors: true + cors: false service: - text: OAuth coding: diff --git a/static/api-docs/openapi.yaml b/static/api-docs/openapi.yaml index 5d20450a6..e343cce3a 100755 --- a/static/api-docs/openapi.yaml +++ b/static/api-docs/openapi.yaml @@ -1994,7 +1994,7 @@ components: type: string documentation: The offset used for result pagination security: - cors: true + cors: false service: - text: OAuth coding: @@ -2376,7 +2376,7 @@ components: - `identifier=https://bluebutton.cms.gov/resources/identifier/hicn-hash|` - `identifier=https://bluebutton.cms.gov/resources/identifier/mbi-hash|` security: - cors: true + cors: false service: - text: OAuth coding: @@ -2499,7 +2499,7 @@ components: type: token documentation: A patient identifier security: - cors: true + cors: false service: - text: OAuth coding: