Skip to content

Commit b5ee9ec

Browse files
authored
fix: Don't allow group chair to change group parent (ietf-tools#6496)
* fix: Don't allow group chair to change group parent (ietf-tools#6037) * test: Fix test_edit_parent_field, add test_edit_parent (whole form) * test: Verify that the chair can't circumvent the system to change the group parent * fix: 403 if user tries to edit an unknown or hidden field * fix: Give edwg GroupFeatures a parent type This tracks a change that was made directly in the production database to fix the immediate cause of ietf-tools#6037. * Empty commit to trigger github unit test
1 parent a3b4162 commit b5ee9ec

4 files changed

Lines changed: 109 additions & 19 deletions

File tree

ietf/group/forms.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2017-2020, All Rights Reserved
1+
# Copyright The IETF Trust 2017-2023, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -103,6 +103,8 @@ def __init__(self, *args, **kwargs):
103103
else:
104104
field = None
105105

106+
self.hide_parent = kwargs.pop('hide_parent', False)
107+
106108
super(self.__class__, self).__init__(*args, **kwargs)
107109

108110
if not group_features or group_features.has_chartering_process:
@@ -138,18 +140,21 @@ def __init__(self, *args, **kwargs):
138140
self.fields['acronym'].widget.attrs['readonly'] = ""
139141

140142
# Sort out parent options
141-
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type__in=parent_types)
142-
if need_parent:
143-
self.fields['parent'].required = True
144-
self.fields['parent'].empty_label = None
145-
# if this is a new group, fill in the default parent, if any
146-
if self.group is None or (not hasattr(self.group, 'pk')):
147-
self.fields['parent'].initial = self.fields['parent'].queryset.filter(
148-
acronym=default_parent
149-
).first()
150-
# label the parent field as 'IETF Area' if appropriate, for consistency with past behavior
151-
if parent_types.count() == 1 and parent_types.first().pk == 'area':
152-
self.fields['parent'].label = "IETF Area"
143+
if self.hide_parent:
144+
self.fields.pop('parent')
145+
else:
146+
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type__in=parent_types)
147+
if need_parent:
148+
self.fields['parent'].required = True
149+
self.fields['parent'].empty_label = None
150+
# if this is a new group, fill in the default parent, if any
151+
if self.group is None or (not hasattr(self.group, 'pk')):
152+
self.fields['parent'].initial = self.fields['parent'].queryset.filter(
153+
acronym=default_parent
154+
).first()
155+
# label the parent field as 'IETF Area' if appropriate, for consistency with past behavior
156+
if parent_types.count() == 1 and parent_types.first().pk == 'area':
157+
self.fields['parent'].label = "IETF Area"
153158

154159
if field:
155160
keys = list(self.fields.keys())

ietf/group/tests_info.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2009-2022, All Rights Reserved
1+
# Copyright The IETF Trust 2009-2023, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -946,10 +946,88 @@ def test_edit_description_field(self):
946946
r = self.client.post(url, {
947947
'description': 'Ignored description',
948948
})
949-
self.assertEqual(r.status_code, 302)
949+
self.assertEqual(r.status_code, 403)
950950
group = Group.objects.get(pk=group.pk) # refresh
951951
self.assertEqual(group.description, 'Updated description')
952952

953+
def test_edit_parent(self):
954+
group = GroupFactory.create(type_id='wg', parent=GroupFactory.create(type_id='area'))
955+
chair = RoleFactory(group=group, name_id='chair').person
956+
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action='edit'))
957+
958+
# parent is not shown to group chair
959+
login_testing_unauthorized(self, chair.user.username, url)
960+
r = self.client.get(url)
961+
self.assertEqual(r.status_code, 200)
962+
q = PyQuery(r.content)
963+
self.assertEqual(len(q('form select[name=parent]')), 0)
964+
965+
# view ignores attempt to change parent
966+
old_parent = group.parent
967+
new_parent = GroupFactory(type_id='area')
968+
self.assertNotEqual(new_parent.acronym, group.parent.acronym)
969+
r = self.client.post(url, dict(
970+
name=group.name,
971+
acronym=group.acronym,
972+
state=group.state_id,
973+
parent=new_parent.pk))
974+
self.assertEqual(r.status_code, 302)
975+
group = Group.objects.get(pk=group.pk)
976+
self.assertNotEqual(group.parent, new_parent)
977+
self.assertEqual(group.parent, old_parent)
978+
979+
# parent is shown to AD and Secretariat
980+
for priv_user in ('ad', 'secretary'):
981+
self.client.logout()
982+
login_testing_unauthorized(self, priv_user, url)
983+
r = self.client.get(url)
984+
self.assertEqual(r.status_code, 200)
985+
q = PyQuery(r.content)
986+
self.assertEqual(len(q('form select[name=parent]')), 1)
987+
988+
new_parent = GroupFactory(type_id='area')
989+
self.assertNotEqual(new_parent.acronym, group.parent.acronym)
990+
r = self.client.post(url, dict(
991+
name=group.name,
992+
acronym=group.acronym,
993+
state=group.state_id,
994+
parent=new_parent.pk))
995+
self.assertEqual(r.status_code, 302)
996+
group = Group.objects.get(pk=group.pk)
997+
self.assertEqual(group.parent, new_parent)
998+
999+
def test_edit_parent_field(self):
1000+
group = GroupFactory.create(type_id='wg', parent=GroupFactory.create(type_id='area'))
1001+
chair = RoleFactory(group=group, name_id='chair').person
1002+
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action='edit', field='parent'))
1003+
1004+
# parent is not shown to group chair
1005+
login_testing_unauthorized(self, chair.user.username, url)
1006+
r = self.client.get(url)
1007+
self.assertEqual(r.status_code, 403)
1008+
1009+
# chair is not allowed to change parent
1010+
new_parent = GroupFactory(type_id='area')
1011+
self.assertNotEqual(new_parent.acronym, group.parent.acronym)
1012+
r = self.client.post(url, dict(parent=new_parent.pk))
1013+
self.assertEqual(r.status_code, 403)
1014+
1015+
# parent is shown to AD and Secretariat
1016+
for priv_user in ('ad', 'secretary'):
1017+
self.client.logout()
1018+
login_testing_unauthorized(self, priv_user, url)
1019+
r = self.client.get(url)
1020+
self.assertEqual(r.status_code, 200)
1021+
q = PyQuery(r.content)
1022+
self.assertEqual(len(q('form select[name=parent]')), 1)
1023+
1024+
new_parent = GroupFactory(type_id='area')
1025+
self.assertNotEqual(new_parent.acronym, group.parent.acronym)
1026+
r = self.client.post(url, dict(parent=new_parent.pk))
1027+
self.assertEqual(r.status_code, 302)
1028+
group = Group.objects.get(pk=group.pk)
1029+
self.assertEqual(group.parent, new_parent)
1030+
9531031
def test_conclude(self):
9541032
group = GroupFactory(acronym="mars")
9551033

ietf/group/views.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -945,14 +945,17 @@ def diff(attr, name):
945945
if not (can_manage_group(request.user, group)
946946
or group.has_role(request.user, group.features.groupman_roles)):
947947
permission_denied(request, "You don't have permission to access this view")
948+
hide_parent = not has_role(request.user, ("Secretariat", "Area Director", "IRTF Chair"))
948949
else:
949950
# This allows ADs to create RG and the IRTF Chair to create WG, but we trust them not to
950951
if not has_role(request.user, ("Secretariat", "Area Director", "IRTF Chair")):
951952
permission_denied(request, "You don't have permission to access this view")
952-
953+
hide_parent = False
953954

954955
if request.method == 'POST':
955-
form = GroupForm(request.POST, group=group, group_type=group_type, field=field)
956+
form = GroupForm(request.POST, group=group, group_type=group_type, field=field, hide_parent=hide_parent)
957+
if field and not form.fields:
958+
permission_denied(request, "You don't have permission to edit this field")
956959
if form.is_valid():
957960
clean = form.cleaned_data
958961
if new_group:
@@ -1114,7 +1117,9 @@ def diff(attr, name):
11141117

11151118
else:
11161119
init = dict()
1117-
form = GroupForm(initial=init, group=group, group_type=group_type, field=field)
1120+
form = GroupForm(initial=init, group=group, group_type=group_type, field=field, hide_parent=hide_parent)
1121+
if field and not form.fields:
1122+
permission_denied(request, "You don't have permission to edit this field")
11181123

11191124
return render(request, 'group/edit.html',
11201125
dict(group=group,

ietf/name/fixtures/names.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3034,7 +3034,9 @@
30343034
"material_types": "[\n \"slides\"\n]",
30353035
"matman_roles": "[\n \"chair\"\n]",
30363036
"need_parent": false,
3037-
"parent_types": [],
3037+
"parent_types": [
3038+
"rfcedtyp"
3039+
],
30383040
"req_subm_approval": true,
30393041
"role_order": "[\n \"chair\"\n]",
30403042
"session_purposes": "[\n \"regular\"\n]",

0 commit comments

Comments
 (0)