Skip to content

Commit 3b3fa2f

Browse files
fix: add resolvability check before redirecting to prevent insecure redirects after publishing (#436)
* refactor: implement resolvability check before redirecting * refactor: used get_preview_url from helpers * refactor: test clean for ON_PUBLISH_REDIRECT setting * refactor: check for redirect url resolvable before redirecting * fix ruff linting issue * Update admin.py * Update test_admin.py to satisfy ruff * Update factories.py * refactor: requested redirect or get_absolute_url --------- Co-authored-by: Fabian Braun <fsbraun@gmx.de>
1 parent 59e06b9 commit 3b3fa2f

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

djangocms_versioning/admin.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,12 +1038,12 @@ def publish_view(self, request, object_id):
10381038

10391039
if not version.can_be_published():
10401040
self.message_user(request, _("Version cannot be published"), messages.ERROR)
1041-
return redirect(requested_redirect or redirect_url)
1041+
return self._internal_redirect(requested_redirect, redirect_url)
10421042
try:
10431043
version.check_publish(request.user)
10441044
except ConditionFailed as e:
10451045
self.message_user(request, force_str(e), messages.ERROR)
1046-
return redirect(requested_redirect or redirect_url)
1046+
return self._internal_redirect(requested_redirect, redirect_url)
10471047

10481048
# Publish the version
10491049
version.publish(request.user)
@@ -1054,9 +1054,25 @@ def publish_view(self, request, object_id):
10541054
# Redirect to published?
10551055
if conf.ON_PUBLISH_REDIRECT == "published":
10561056
if hasattr(version.content, "get_absolute_url"):
1057-
redirect_url = version.content.get_absolute_url() or redirect_url
1057+
requested_redirect = requested_redirect or version.content.get_absolute_url()
1058+
1059+
return self._internal_redirect(requested_redirect, redirect_url)
1060+
1061+
1062+
def _internal_redirect(self, url, fallback):
1063+
"""Helper function to check if the give URL is resolvable
1064+
If resolvable, return the URL; otherwise, returns the fallback URL.
1065+
"""
1066+
if not url:
1067+
return redirect(fallback)
1068+
1069+
try:
1070+
resolve(url)
1071+
except Resolver404:
1072+
return redirect(fallback)
1073+
1074+
return redirect(url)
10581075

1059-
return redirect(requested_redirect or redirect_url)
10601076

10611077
def unpublish_view(self, request, object_id):
10621078
"""Unpublishes the specified version and redirects back to the

tests/test_admin.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,46 @@ def test_publish_view_redirects_according_to_settings(self):
13841384

13851385
conf.ON_PUBLISH_REDIRECT = original_setting
13861386

1387+
1388+
def test_publish_resolvable_redirect_url(self):
1389+
from djangocms_versioning import conf
1390+
1391+
original_setting = conf.ON_PUBLISH_REDIRECT
1392+
conf.ON_PUBLISH_REDIRECT = "published"
1393+
1394+
user = self.get_superuser()
1395+
poll_version = factories.PollVersionFactory(state=constants.DRAFT)
1396+
1397+
# when there is no requested redirect
1398+
url = self.get_admin_url(
1399+
self.versionable.version_model_proxy, "publish", poll_version.pk
1400+
)
1401+
1402+
with self.login_user_context(user):
1403+
response = self.client.post(url)
1404+
1405+
self.assertEqual(poll_version.content.get_absolute_url(), response.url)
1406+
1407+
# when the requested url is resolvable
1408+
resolvable_url = url + "?next=" + helpers.get_preview_url(poll_version.content)
1409+
1410+
with self.login_user_context(user):
1411+
response = self.client.post(resolvable_url)
1412+
1413+
self.assertEqual(response.url, helpers.get_preview_url(poll_version.content))
1414+
1415+
# when the requested url is not resolvable, should default to version list url
1416+
not_resolvable_url = url + "?next=http://example.com"
1417+
1418+
with self.login_user_context(user):
1419+
response = self.client.post(not_resolvable_url)
1420+
1421+
self.assertEqual(response.url, helpers.get_preview_url(poll_version.content))
1422+
1423+
conf.ON_PUBLISH_REDIRECT = original_setting
1424+
1425+
1426+
13871427
def test_published_view_sets_modified_time(self):
13881428
poll_version = factories.PollVersionFactory(state=constants.DRAFT)
13891429
url = self.get_admin_url(

0 commit comments

Comments
 (0)