Skip to content

Commit

Permalink
allow multiple valid cors origins for gce (#2470)
Browse files Browse the repository at this point in the history
Originally we just had a single origin that was allowed for GCE requests. Now we follow the same pattern as we use in WOW, specifying a list of valid origins and a list of regexes of origins in the django settings file. Now all the prod/demo/prviews links should work
  • Loading branch information
austensen authored Jan 29, 2025
1 parent a268f25 commit 31b60b2
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 13 deletions.
11 changes: 11 additions & 0 deletions gce/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,14 @@ def test_invalid_origin_fails(client, settings):
)
assert res.status_code == 403
assert res.json()["error"] == "Invalid origin"


@pytest.mark.django_db
def test_valid_origin_works(client, settings):
res = client.post(
"/gce/upload",
json.dumps(DATA_STEP_1),
**base_headers(settings),
HTTP_ORIGIN="https://deploy-preview-89--demo-gce-screener.netlify.app",
)
assert res.status_code == 200
28 changes: 19 additions & 9 deletions gce/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import json
import re
from typing import Any, Dict, Literal, Optional, Set
from typing import Any, Dict, Literal, Optional

import pydantic
from django.conf import settings
Expand Down Expand Up @@ -156,22 +156,31 @@ def as_json_response(self):
)


def is_origin_valid(origin: str, valid_origins: Set[str]) -> bool:
def is_valid_origin(request):
origin: str = request.META.get("HTTP_ORIGIN", "")
host_origin = request.build_absolute_uri("/")[:-1]
valid_origins = set(settings.GCE_CORS_ALLOWED_ORIGINS + [host_origin])
if "*" in valid_origins:
return True
return origin in valid_origins
if origin in valid_origins:
return True
for pattern in settings.GCE_CORS_ALLOWED_ORIGIN_REGEXES:
if re.match(pattern, origin):
return True
return False


def validate_origin(request):
origin: str = request.META.get("HTTP_ORIGIN", "")
host_origin = request.build_absolute_uri("/")[:-1]
valid_origins = set([settings.GCE_ORIGIN, host_origin])
if not is_origin_valid(origin, valid_origins):
if not is_valid_origin(request):
raise InvalidOriginError(f"{origin} is not a valid origin")


def apply_cors_policy(response):
response["Access-Control-Allow-Origin"] = settings.GCE_ORIGIN
def apply_cors_policy(request, response):
origin: str = request.META.get("HTTP_ORIGIN", "")
response["Access-Control-Allow-Origin"] = (
origin if is_valid_origin(request) else settings.GCE_ORIGIN
)
response["Access-Control-Allow-Methods"] = "OPTIONS,POST"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "X-Requested-With, Content-Type, Authorization"
Expand All @@ -187,6 +196,7 @@ def api(fn):
def wrapper(request, *args, **kwargs):
request.is_api_request = True
try:
validate_origin(request)
response = fn(request, *args, **kwargs)
except (DataValidationError, AuthorizationError, InvalidOriginError) as e:
logger.error(e)
Expand All @@ -205,6 +215,6 @@ def wrapper(request, *args, **kwargs):
content_type="application/json",
status=500,
)
return apply_cors_policy(response)
return apply_cors_policy(request, response)

return wrapper
3 changes: 1 addition & 2 deletions gce/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt

from gce.util import GcePostData, api, authorize_with_token, validate_data, validate_origin
from gce.util import GcePostData, api, authorize_with_token, validate_data
from gce.models import GoodCauseEvictionScreenerResponse


Expand All @@ -15,7 +15,6 @@ def upload(request):
The POST endpoint used to record user responses from the standalone Good
Cause Eviction screener tool.
"""
validate_origin(request)

if request.method == "OPTIONS":
return HttpResponse(status=200)
Expand Down
4 changes: 2 additions & 2 deletions project/justfix_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ class JustfixEnvironment(typed_environ.BaseEnvironment):
# The base url for outbound links to Eviction Free NYC.
EFNYC_ORIGIN: str = "https://www.evictionfreenyc.org"

# The base url for cors policy for Good Cause Eviction screener.
GCE_ORIGIN: str = "https://gce-screener.netlify.app"
# The base url for outbound links to Good Cause NYC.
GCE_ORIGIN: str = "https://goodcausenyc.org"

# Whether to use the lambda HTTP server. If false, we'll use a separate
# subprocess for each server-side rendering request, otherwise we'll
Expand Down
15 changes: 15 additions & 0 deletions project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,21 @@

GCE_API_TOKEN = env.GCE_API_TOKEN

GCE_CORS_ALLOWED_ORIGINS = [
"https://gce-screener.netlify.app",
"https://demo-gce-screener.netlify.app",
"https://goodcausenyc.org",
"https://goodcauseny.org",
"http://0.0.0.0:3000",
]

GCE_CORS_ALLOWED_ORIGIN_REGEXES = [
r"https://deploy-preview-(?:\d{1,4})--gce-screener\.netlify\.app",
r"https://deploy-preview-(?:\d{1,4})--demo-gce-screener\.netlify\.app",
r"https://([A-Za-z0-9\-\_]+)--gce-screener\.netlify\.app",
r"https://([A-Za-z0-9\-\_]+)--demo-gce-screener\.netlify\.app",
]

CONTENTFUL_SPACE_ID = env.CONTENTFUL_SPACE_ID

CONTENTFUL_ACCESS_TOKEN = env.CONTENTFUL_ACCESS_TOKEN
Expand Down

0 comments on commit 31b60b2

Please sign in to comment.