Skip to content

Commit 0e93ce4

Browse files
Validate user as admin of subproject when adding it to project
It was previously possible to add subprojects without beeing admin of them. It should be required to be a admin. Otherwise people end up getting incorporated into a project without knowing it or beeing asked. Related to #1122
1 parent 71c3b2b commit 0e93ce4

File tree

4 files changed

+82
-11
lines changed

4 files changed

+82
-11
lines changed

readthedocs/projects/forms.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ class SubprojectForm(forms.Form):
309309
subproject = forms.CharField()
310310

311311
def __init__(self, *args, **kwargs):
312-
self.parent = kwargs.pop('parent', None)
312+
self.user = kwargs.pop('user')
313+
self.parent = kwargs.pop('parent')
313314
super(SubprojectForm, self).__init__(*args, **kwargs)
314315

315316
def clean_subproject(self):
@@ -318,11 +319,16 @@ def clean_subproject(self):
318319
if not subproject_qs.exists():
319320
raise forms.ValidationError((_("Project %(name)s does not exist")
320321
% {'name': subproject_name}))
321-
self.subproject = subproject_qs[0]
322-
return subproject_name
322+
subproject = subproject_qs.first()
323+
if not subproject.user_is_admin(self.user):
324+
raise forms.ValidationError(_(
325+
'You need to be admin of {name} in order to add it as '
326+
'a subproject.'.format(name=subproject_name)))
327+
return subproject
323328

324329
def save(self):
325-
relationship = self.parent.add_subproject(self.subproject)
330+
relationship = self.parent.add_subproject(
331+
self.cleaned_data['subproject'])
326332
return relationship
327333

328334

readthedocs/projects/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ def sync_supported_versions(self):
253253
verbose_name__in=supported).update(supported=False)
254254
self.versions.filter(verbose_name=LATEST_VERBOSE_NAME).update(supported=True)
255255

256+
def user_is_admin(self, user):
257+
return user in self.users.all()
258+
256259
def save(self, *args, **kwargs):
257260
first_save = self.pk is None
258261
if not self.slug:

readthedocs/projects/views/private.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -400,13 +400,19 @@ def project_subprojects(request, project_slug):
400400
project = get_object_or_404(Project.objects.for_admin_user(request.user),
401401
slug=project_slug)
402402

403-
form = SubprojectForm(data=request.POST or None, parent=project)
404-
405-
if request.method == 'POST' and form.is_valid():
406-
form.save()
407-
project_dashboard = reverse(
408-
'projects_subprojects', args=[project.slug])
409-
return HttpResponseRedirect(project_dashboard)
403+
form_kwargs = {
404+
'parent': project,
405+
'user': request.user,
406+
}
407+
if request.method == 'POST':
408+
form = SubprojectForm(request.POST, **form_kwargs)
409+
if form.is_valid():
410+
form.save()
411+
project_dashboard = reverse(
412+
'projects_subprojects', args=[project.slug])
413+
return HttpResponseRedirect(project_dashboard)
414+
else:
415+
form = SubprojectForm(**form_kwargs)
410416

411417
subprojects = project.subprojects.all()
412418

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from django.contrib.auth.models import User
2+
from django.test import TestCase
3+
from projects.forms import SubprojectForm
4+
from django_dynamic_fixture import G
5+
6+
from projects.models import Project
7+
8+
9+
class SubprojectFormTests(TestCase):
10+
def test_name_validation(self):
11+
user = G(User)
12+
project = G(Project, slug='mainproject')
13+
14+
form = SubprojectForm({},
15+
parent=project, user=user)
16+
form.full_clean()
17+
self.assertTrue('subproject' in form.errors)
18+
19+
form = SubprojectForm({'name': 'not-existent'},
20+
parent=project, user=user)
21+
form.full_clean()
22+
self.assertTrue('subproject' in form.errors)
23+
24+
def test_adding_subproject_fails_when_user_is_not_admin(self):
25+
# Make sure that a user cannot add a subproject that he is not the
26+
# admin of.
27+
28+
user = G(User)
29+
project = G(Project, slug='mainproject')
30+
project.users.add(user)
31+
subproject = G(Project, slug='subproject')
32+
33+
form = SubprojectForm({'subproject': subproject.slug},
34+
parent=project, user=user)
35+
# Fails because user does not own subproject.
36+
form.full_clean()
37+
self.assertTrue('subproject' in form.errors)
38+
39+
def test_admin_of_subproject_can_add_it(self):
40+
user = G(User)
41+
project = G(Project, slug='mainproject')
42+
project.users.add(user)
43+
subproject = G(Project, slug='subproject')
44+
subproject.users.add(user)
45+
46+
# Works now as user is admin of subproject.
47+
form = SubprojectForm({'subproject': subproject.slug},
48+
parent=project, user=user)
49+
# Fails because user does not own subproject.
50+
form.full_clean()
51+
self.assertTrue(form.is_valid())
52+
form.save()
53+
54+
self.assertEqual(
55+
[r.child for r in project.subprojects.all()],
56+
[subproject])

0 commit comments

Comments
 (0)