Skip to content

Commit

Permalink
Merge pull request #3124 from open-formulieren/feature/subpath-support
Browse files Browse the repository at this point in the history
Support hosting on subpaths
  • Loading branch information
sergei-maertens authored Jun 6, 2023
2 parents d75e5f0 + a1196d7 commit 3681101
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 4 deletions.
5 changes: 4 additions & 1 deletion bin/docker_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ uwsgi_port=${UWSGI_PORT:-8000}
uwsgi_processes=${UWSGI_PROCESSES:-4}
uwsgi_threads=${UWSGI_THREADS:-1}

mountpoint=${SUBPATH:-/}

until pg_isready; do
>&2 echo "Waiting for database connection..."
sleep 1
Expand All @@ -29,7 +31,8 @@ python src/manage.py migrate
exec uwsgi \
--http :$uwsgi_port \
--http-keepalive \
--module openforms.wsgi \
--mount $mountpoint=openforms.wsgi:application \
--manage-script-name \
--static-map /static=/app/static \
--static-map /media=/app/media \
--chdir src \
Expand Down
2 changes: 2 additions & 0 deletions docs/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ Other settings
* ``FORMS_EXPORT_REMOVED_AFTER_DAYS``: The number of days after which zip files of exported forms should be deleted.
Defaults to 7 days.

* ``SUBPATH``: A string with a prefix for all URL paths, for example ``/openforms``. Typically used at the infrastructure level to route to a particular application on the same (sub)domain. Defaults to empty string meaning that Open Forms is hosted at the root (``/``).

.. _`Django DATABASE settings`: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASE-ENGINE

Specifying the environment variables
Expand Down
11 changes: 11 additions & 0 deletions src/openforms/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,17 @@

MAX_FILE_UPLOAD_SIZE = config("MAX_FILE_UPLOAD_SIZE", default="50M", cast=Filesize())

# Deal with being hosted on a subpath
SUBPATH = config("SUBPATH", default="")
if SUBPATH:
if not SUBPATH.startswith("/"):
SUBPATH = f"/{SUBPATH}"

if SUBPATH != "/":
STATIC_URL = f"{SUBPATH}{STATIC_URL}"
MEDIA_URL = f"{SUBPATH}{MEDIA_URL}"


##############################
# #
# 3RD PARTY LIBRARY SETTINGS #
Expand Down
4 changes: 2 additions & 2 deletions src/openforms/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __call__(self, request: HttpRequest):
response = self.get_response(request)

# Only add the CSRF token header if it's an api endpoint
if not request.path.startswith("/api"):
if not request.path_info.startswith("/api"):
return response

response[CSRF_TOKEN_HEADER_NAME] = get_token(request)
Expand All @@ -67,7 +67,7 @@ def __call__(self, request: HttpRequest):
response = self.get_response(request)

# Only add the header if it's an api endpoint
if not request.path.startswith("/api"):
if not request.path_info.startswith("/api"):
return response

response[IS_FORM_DESIGNER_HEADER_NAME] = "false"
Expand Down
27 changes: 27 additions & 0 deletions src/openforms/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ def test_csrftoken_not_in_header_root(self):

self.assertNotIn(CSRF_TOKEN_HEADER_NAME, response.headers)

def test_csrftoken_in_header_api_endpoint_with_subpath(self):
url = reverse("api:form-list")

response = self.client.get(url, SCRIPT_NAME="/of")

self.assertIn(CSRF_TOKEN_HEADER_NAME, response.headers)

def test_csrftoken_not_in_header_root_with_subpath(self):
response = self.client.get("/", SCRIPT_NAME="/of")

self.assertNotIn(CSRF_TOKEN_HEADER_NAME, response.headers)


class CanNavigateBetweenStepsMiddlewareTests(TestCase):
def test_header_api_endpoint_not_authenticated(self):
Expand Down Expand Up @@ -63,3 +75,18 @@ def test_header_api_endpoint_staff_with_permissions(self):

self.assertIn(IS_FORM_DESIGNER_HEADER_NAME, response.headers)
self.assertEqual("true", response.headers[IS_FORM_DESIGNER_HEADER_NAME])

def test_header_not_api_endpoint_with_subpath(self):
response = self.client.get("/", SCRIPT_NAME="/of")

self.assertNotIn(IS_FORM_DESIGNER_HEADER_NAME, response.headers)

def test_header_api_endpoint_staff_with_permissions_with_subpath(self):
user = StaffUserFactory.create(user_permissions=["forms.change_form"])
self.client.force_login(user=user)
url = reverse("api:form-list")

response = self.client.get(url, SCRIPT_NAME="/of")

self.assertIn(IS_FORM_DESIGNER_HEADER_NAME, response.headers)
self.assertEqual("true", response.headers[IS_FORM_DESIGNER_HEADER_NAME])
25 changes: 25 additions & 0 deletions src/openforms/utils/tests/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,28 @@ def test_is_admin_request_direct(self):
result = is_admin_request(request)

self.assertTrue(result)

def test_is_admin_request_true_with_subpath(self):
factory = RequestFactory()
request = factory.get(
"/api/v1/foo",
HTTP_REFERER="http://testserver/admin/forms/form/1/change/",
SCRIPT_NAME="/of",
)

self.assertTrue(is_admin_request(request))

def test_is_admin_request_false_with_subpath(self):
factory = RequestFactory()
bad_referers = (
"http://otherdomain/bar/",
"http://otherdomain/admin/forms/form/",
)

for referer in bad_referers:
with self.subTest(referer=referer):
request = factory.get(
"/api/v1/foo", HTTP_REFERER=referer, SCRIPT_NAME="/of"
)

self.assertFalse(is_admin_request(request))
2 changes: 1 addition & 1 deletion src/openforms/utils/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def is_admin_request(request: RequestType) -> bool:
:arg request: the request object to be checked.
"""
admin_path_prefix = reverse("admin:index")
if request.path.startswith(admin_path_prefix):
if request.path_info.startswith(admin_path_prefix):
return True
if not (referrer := request.headers.get("Referer")):
return False
Expand Down

0 comments on commit 3681101

Please sign in to comment.