mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
Fixed CVE-2019-19118 -- Required edit permissions on parent model for editable inlines in admin.
Thank you to Shen Ying for reporting this issue.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
from django.contrib.admin import ModelAdmin, TabularInline
|
||||
from django.contrib.admin.helpers import InlineAdminForm
|
||||
from django.contrib.admin.tests import AdminSeleniumTestCase
|
||||
@@ -862,6 +864,98 @@ class TestInlinePermissions(TestCase):
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_inlines.urls')
|
||||
class TestReadOnlyChangeViewInlinePermissions(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = User.objects.create_user('testing', password='password', is_staff=True)
|
||||
cls.user.user_permissions.add(
|
||||
Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll))
|
||||
)
|
||||
cls.user.user_permissions.add(
|
||||
*Permission.objects.filter(
|
||||
codename__endswith="question", content_type=ContentType.objects.get_for_model(Question)
|
||||
).values_list('pk', flat=True)
|
||||
)
|
||||
|
||||
cls.poll = Poll.objects.create(name="Survey")
|
||||
cls.add_url = reverse('admin:admin_inlines_poll_add')
|
||||
cls.change_url = reverse('admin:admin_inlines_poll_change', args=(cls.poll.id,))
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_add_url_not_allowed(self):
|
||||
response = self.client.get(self.add_url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.post(self.add_url, {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_post_to_change_url_not_allowed(self):
|
||||
response = self.client.post(self.change_url, {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_to_change_url_is_allowed(self):
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_main_model_is_rendered_as_read_only(self):
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<div class="readonly">%s</div>' % self.poll.name,
|
||||
html=True
|
||||
)
|
||||
input = '<input type="text" name="name" value="%s" class="vTextField" maxlength="40" required id="id_name">'
|
||||
self.assertNotContains(
|
||||
response,
|
||||
input % self.poll.name,
|
||||
html=True
|
||||
)
|
||||
|
||||
def test_inlines_are_rendered_as_read_only(self):
|
||||
question = Question.objects.create(text="How will this be rendered?", poll=self.poll)
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<td class="field-text"><p>%s</p></td>' % question.text,
|
||||
html=True
|
||||
)
|
||||
self.assertNotContains(response, 'id="id_question_set-0-text"')
|
||||
self.assertNotContains(response, 'id="id_related_objs-0-DELETE"')
|
||||
|
||||
def test_submit_line_shows_only_close_button(self):
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<a href="/admin/admin_inlines/poll/" class="closelink">Close</a>',
|
||||
html=True
|
||||
)
|
||||
delete_link = '<p class="deletelink-box"><a href="/admin/admin_inlines/poll/%s/delete/" class="deletelink">Delete</a></p>' # noqa
|
||||
self.assertNotContains(
|
||||
response,
|
||||
delete_link % self.poll.id,
|
||||
html=True
|
||||
)
|
||||
self.assertNotContains(response, '<input type="submit" value="Save and add another" name="_addanother">')
|
||||
self.assertNotContains(response, '<input type="submit" value="Save and continue editing" name="_continue">')
|
||||
|
||||
def test_inline_delete_buttons_are_not_shown(self):
|
||||
Question.objects.create(text="How will this be rendered?", poll=self.poll)
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertNotContains(
|
||||
response,
|
||||
'<input type="checkbox" name="question_set-0-DELETE" id="id_question_set-0-DELETE">',
|
||||
html=True
|
||||
)
|
||||
|
||||
def test_extra_inlines_are_not_shown(self):
|
||||
response = self.client.get(self.change_url)
|
||||
self.assertNotContains(response, 'id="id_question_set-0-text"')
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_inlines.urls')
|
||||
class SeleniumTests(AdminSeleniumTestCase):
|
||||
|
||||
@@ -1057,6 +1151,24 @@ class SeleniumTests(AdminSeleniumTestCase):
|
||||
self.assertEqual(ProfileCollection.objects.all().count(), 1)
|
||||
self.assertEqual(Profile.objects.all().count(), 3)
|
||||
|
||||
def test_add_inline_link_absent_for_view_only_parent_model(self):
|
||||
user = User.objects.create_user('testing', password='password', is_staff=True)
|
||||
user.user_permissions.add(
|
||||
Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll))
|
||||
)
|
||||
user.user_permissions.add(
|
||||
*Permission.objects.filter(
|
||||
codename__endswith="question", content_type=ContentType.objects.get_for_model(Question)
|
||||
).values_list('pk', flat=True)
|
||||
)
|
||||
self.admin_login(username='testing', password='password')
|
||||
poll = Poll.objects.create(name="Survey")
|
||||
change_url = reverse('admin:admin_inlines_poll_change', args=(poll.id,))
|
||||
self.selenium.get(self.live_server_url + change_url)
|
||||
with self.disable_implicit_wait():
|
||||
with self.assertRaises(NoSuchElementException):
|
||||
self.selenium.find_element_by_link_text('Add another Question')
|
||||
|
||||
def test_delete_inlines(self):
|
||||
self.admin_login(username='super', password='secret')
|
||||
self.selenium.get(self.live_server_url + reverse('admin:admin_inlines_profilecollection_add'))
|
||||
|
||||
Reference in New Issue
Block a user