Merge lp:~salgado/offspring/private-projects-views into lp:~linaro-automation/offspring/linaro

Proposed by Guilherme Salgado
Status: Merged
Approved by: James Tunnicliffe
Approved revision: no longer in the source branch.
Merge reported by: Guilherme Salgado
Merged at revision: not available
Proposed branch: lp:~salgado/offspring/private-projects-views
Merge into: lp:~linaro-automation/offspring/linaro
Prerequisite: lp:~salgado/offspring/private-projects
Diff against target: 387 lines (+269/-26)
5 files modified
Makefile (+6/-0)
lib/offspring/web/queuemanager/tests/__init__.py (+1/-0)
lib/offspring/web/queuemanager/tests/factory.py (+5/-5)
lib/offspring/web/queuemanager/tests/test_views.py (+201/-0)
lib/offspring/web/queuemanager/views.py (+56/-21)
To merge this branch: bzr merge lp:~salgado/offspring/private-projects-views
Reviewer Review Type Date Requested Status
James Tunnicliffe (community) Approve
Review via email: mp+78850@code.launchpad.net

Description of the change

Make all project and build views return a 404 if the user is not allowed to see that private project/build.

To post a comment you must log in.
Revision history for this message
James Tunnicliffe (dooferlad) wrote :

Looks good.

review: Approve
Revision history for this message
Guilherme Salgado (salgado) wrote :

In fact this was merged into our integration branch (lp:~linaro-infrastructure/offspring/private-builds)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2011-10-10 14:33:25 +0000
+++ Makefile 2011-10-10 14:33:25 +0000
@@ -36,6 +36,12 @@
36 tar --directory=lib/offspring/web/media/js/ -zxf .download-cache/SmartClientRuntime-7.0.tgz36 tar --directory=lib/offspring/web/media/js/ -zxf .download-cache/SmartClientRuntime-7.0.tgz
37 make37 make
3838
39web-run: web
40 ./bin/offspring-web runserver --settings=offspring.web.settings_devel
41
42web-shell: web
43 ./bin/offspring-web shell --settings=offspring.web.settings_devel
44
39install-test-runner: .virtualenv .download-cache45install-test-runner: .virtualenv .download-cache
40 $(PIP_INSTALL) nose46 $(PIP_INSTALL) nose
4147
4248
=== modified file 'lib/offspring/web/queuemanager/tests/__init__.py'
--- lib/offspring/web/queuemanager/tests/__init__.py 2011-10-10 14:33:25 +0000
+++ lib/offspring/web/queuemanager/tests/__init__.py 2011-10-10 14:33:25 +0000
@@ -1,1 +1,2 @@
1from .test_models import *1from .test_models import *
2from .test_views import *
23
=== modified file 'lib/offspring/web/queuemanager/tests/factory.py'
--- lib/offspring/web/queuemanager/tests/factory.py 2011-10-10 14:33:25 +0000
+++ lib/offspring/web/queuemanager/tests/factory.py 2011-10-10 14:33:25 +0000
@@ -37,10 +37,8 @@
3737
38 def makeUser(self):38 def makeUser(self):
39 userid = password = self.getUniqueString()39 userid = password = self.getUniqueString()
40 user = User.objects.create_user(40 return User.objects.create_user(
41 userid, self.getUniqueEmailAddress(), password)41 userid, self.getUniqueEmailAddress(), password)
42 user.save()
43 return user
4442
45 def makeProject(self, name=None, title=None, is_private=False,43 def makeProject(self, name=None, title=None, is_private=False,
46 project_group=None, owner=None, access_groups=[]):44 project_group=None, owner=None, access_groups=[]):
@@ -68,11 +66,13 @@
68 group.save()66 group.save()
69 return group67 return group
7068
71 def makeBuildResult(self, project=None):69 def makeBuildResult(self, project=None, name=None, result=None):
70 if name is None:
71 name = self.getUniqueString()
72 if project is None:72 if project is None:
73 project = self.makeProject()73 project = self.makeProject()
74 project.save()74 project.save()
75 result = BuildResult(project=project)75 result = BuildResult(name=name, project=project, result=result)
76 result.save()76 result.save()
77 return result77 return result
7878
7979
=== added file 'lib/offspring/web/queuemanager/tests/test_views.py'
--- lib/offspring/web/queuemanager/tests/test_views.py 1970-01-01 00:00:00 +0000
+++ lib/offspring/web/queuemanager/tests/test_views.py 2011-10-10 14:33:25 +0000
@@ -0,0 +1,201 @@
1from django.core.urlresolvers import reverse
2from django.contrib.auth.models import (
3 AnonymousUser,
4 Group,
5 Permission,
6 )
7from django.http import Http404
8from django.test import TestCase
9
10from offspring.enums import ProjectBuildStates
11from offspring.web.queuemanager.models import (
12 Project,
13 )
14from offspring.web.queuemanager.views import get_possibly_private_object
15from offspring.web.queuemanager.tests.factory import factory
16
17
18class Test_get_possibly_private_object(TestCase):
19
20 def test_public_object(self):
21 obj = factory.makeProject(is_private=False)
22 obj2 = get_possibly_private_object(
23 AnonymousUser(), Project, pk=obj.pk)
24 self.assertEqual(obj.pk, obj2.pk)
25
26 def test_private_object_for_owner(self):
27 obj = factory.makeProject(is_private=True)
28 obj2 = get_possibly_private_object(obj.owner, Project, pk=obj.pk)
29 self.assertEqual(obj.pk, obj2.pk)
30
31 def test_private_object_for_user_with_no_access(self):
32 obj = factory.makeProject(is_private=True)
33 user = factory.makeUser()
34 self.assertRaises(
35 Http404, get_possibly_private_object, user, Project, pk=obj.pk)
36
37
38class ProjectViewTestsMixin(object):
39 view_path = None
40
41 def get_expected_page_heading(self, project):
42 raise NotImplementedError()
43
44 def grant_necessary_permission_to(self, user):
45 pass
46
47 def test_private_project_visible(self):
48 project = factory.makeProject(is_private=True)
49 user = project.owner
50 self.grant_necessary_permission_to(user)
51 self.assertTrue(
52 self.client.login(username=user.username, password=user.username))
53 response = self.client.get(
54 reverse(self.view_path, args=[project.name]))
55 self.assertContains(
56 response, self.get_expected_page_heading(project),
57 status_code=200, msg_prefix=response.content)
58
59 def test_private_project_invisible(self):
60 project = factory.makeProject(is_private=True)
61 user = factory.makeUser()
62 self.grant_necessary_permission_to(user)
63 self.assertTrue(
64 self.client.login(username=user.username, password=user.username))
65 response = self.client.get(
66 reverse(self.view_path, args=[project.name]))
67 self.assertEqual(404, response.status_code)
68
69
70class ProjectDetailsViewTests(TestCase, ProjectViewTestsMixin):
71 view_path = 'offspring.web.queuemanager.views.project_details'
72
73 def get_expected_page_heading(self, project):
74 return 'Project Details for %s' % project.title.capitalize()
75
76 def test_public_project(self):
77 project = factory.makeProject(is_private=False)
78 response = self.client.get(
79 reverse(self.view_path, args=[project.name]))
80 self.assertContains(
81 response, self.get_expected_page_heading(project),
82 status_code=200, msg_prefix=response.content)
83
84
85class ProjectEditViewTests(TestCase, ProjectViewTestsMixin):
86 view_path = 'offspring.web.queuemanager.views.project_edit'
87
88 def get_expected_page_heading(self, project):
89 return 'Update details for %s' % project.title.capitalize()
90
91 def grant_necessary_permission_to(self, user):
92 grant_permission_to_user(user, 'change_project')
93
94 def test_public_project(self):
95 project = factory.makeProject(is_private=False)
96 user = factory.makeUser()
97 self.grant_necessary_permission_to(user)
98 self.assertTrue(
99 self.client.login(username=user.username, password=user.username))
100 response = self.client.get(
101 reverse(self.view_path, args=[project.name]))
102 self.assertContains(
103 response, self.get_expected_page_heading(project),
104 status_code=200, msg_prefix=response.content)
105
106
107class BuildViewTests(TestCase):
108
109 def test_public_build(self):
110 project = factory.makeProject(is_private=False)
111 build = factory.makeBuildResult(project=project)
112 response = self.client.get(
113 reverse('offspring.web.queuemanager.views.build_details',
114 args=[project.name, build.name]))
115 self.assertContains(
116 response, 'Build Details', status_code=200,
117 msg_prefix=response.content)
118
119 def test_private_build_visible(self):
120 project = factory.makeProject(is_private=True)
121 build = factory.makeBuildResult(project=project)
122 user = project.owner
123 self.assertTrue(
124 self.client.login(username=user.username, password=user.username))
125 response = self.client.get(
126 reverse('offspring.web.queuemanager.views.build_details',
127 args=[project.name, build.name]))
128 self.assertContains(
129 response, 'Build Details', status_code=200,
130 msg_prefix=response.content)
131
132 def test_private_build_invisible(self):
133 project = factory.makeProject(is_private=True)
134 build = factory.makeBuildResult(project=project)
135 self.assertTrue(make_user_and_login(self.client))
136 response = self.client.get(
137 reverse('offspring.web.queuemanager.views.build_details',
138 args=[project.name, build.name]))
139 self.assertEqual(404, response.status_code)
140
141
142class BuildPublishViewTests(TestCase):
143
144 def test_public_build(self):
145 project = factory.makeProject(is_private=False)
146 build = factory.makeBuildResult(
147 project=project, result=ProjectBuildStates.SUCCESS)
148 user = factory.makeUser()
149 grant_permission_to_user(user, 'add_release')
150 self.assertTrue(
151 self.client.login(username=user.username, password=user.username))
152 response = self.client.get(
153 reverse('offspring.web.queuemanager.views.build_publish',
154 args=[project.name, build.name]))
155 self.assertContains(
156 response, 'Release %s build' % project.title.capitalize(),
157 status_code=200, msg_prefix=response.content)
158
159 def test_private_build_visible(self):
160 project = factory.makeProject(is_private=True)
161 build = factory.makeBuildResult(
162 project=project, result=ProjectBuildStates.SUCCESS)
163 user = build.project.owner
164 grant_permission_to_user(user, 'add_release')
165 self.assertTrue(
166 self.client.login(username=user.username, password=user.username))
167 response = self.client.get(
168 reverse('offspring.web.queuemanager.views.build_publish',
169 args=[project.name, build.name]))
170 self.assertContains(
171 response, 'Release %s build' % project.title.capitalize(),
172 status_code=200, msg_prefix=response.content)
173
174 def test_private_build_invisible(self):
175 project = factory.makeProject(is_private=True)
176 build = factory.makeBuildResult(
177 project=project, result=ProjectBuildStates.SUCCESS)
178 user = factory.makeUser()
179 grant_permission_to_user(user, 'add_release')
180 self.assertTrue(
181 self.client.login(username=user.username, password=user.username))
182 response = self.client.get(
183 reverse('offspring.web.queuemanager.views.build_publish',
184 args=[project.name, build.name]))
185 self.assertEqual(404, response.status_code)
186
187
188def make_user_and_login(client):
189 user = factory.makeUser()
190 return client.login(username=user.username, password=user.username)
191
192
193def grant_permission_to_user(user, permission):
194 group = Group(name=user.username)
195 group.save()
196 group.permissions.add(Permission.objects.get(codename=permission))
197 group.save()
198 user.groups.add(group)
199 user.save()
200 return user
201
0202
=== modified file 'lib/offspring/web/queuemanager/views.py'
--- lib/offspring/web/queuemanager/views.py 2011-10-10 14:33:25 +0000
+++ lib/offspring/web/queuemanager/views.py 2011-10-10 14:33:25 +0000
@@ -16,12 +16,13 @@
16from django.http import (16from django.http import (
17 HttpResponse,17 HttpResponse,
18 HttpResponseRedirect,18 HttpResponseRedirect,
19 HttpResponseForbidden19 HttpResponseForbidden,
20 Http404,
20)21)
21from django.middleware import csrf22from django.middleware import csrf
22from django.shortcuts import (23from django.shortcuts import (
23 render_to_response,24 render_to_response,
24 get_object_or_40425 get_object_or_404,
25)26)
26from django.template import (27from django.template import (
27 Context,28 Context,
@@ -57,6 +58,19 @@
57 Release58 Release
58)59)
5960
61
62def get_possibly_private_object(user, klass, *args, **kwargs):
63 """Use get_object_or_404() to return a possibly private object.
64
65 If the object does not exist or the user doesn't have permission to see
66 it, raise Http404.
67 """
68 obj = get_object_or_404(klass, *args, **kwargs)
69 if not obj.is_visible_to(user):
70 raise Http404('No matches for the given query')
71 return obj
72
73
60#XXX: Reduce horrible duplication that occurs in these functions (mostly done).74#XXX: Reduce horrible duplication that occurs in these functions (mostly done).
61#TODO: Thanks to reduced duplication, lets start using more generic views.75#TODO: Thanks to reduced duplication, lets start using more generic views.
6276
@@ -118,8 +132,11 @@
118132
119133
120def build_details(request, projectName, buildName):134def build_details(request, projectName, buildName):
121 b = get_object_or_404(135 user_visible_builds = BuildResult.all_objects.accessible_by_user(
122 BuildResult.objects.select_related('builder', 'project', 'requestor'),136 request.user)
137 b = get_possibly_private_object(
138 request.user,
139 user_visible_builds.select_related('builder', 'project', 'requestor'),
123 project__pk=projectName, name=buildName)140 project__pk=projectName, name=buildName)
124 pageData = { 141 pageData = {
125 'pillar' : 'builds',142 'pillar' : 'builds',
@@ -134,14 +151,18 @@
134151
135@login_required152@login_required
136def build_publish(request, projectName, buildName):153def build_publish(request, projectName, buildName):
137 p = get_object_or_404(Project, pk=projectName)154 user = request.user
138 b = get_object_or_404(155 p = get_possibly_private_object(user, Project, pk=projectName)
139 BuildResult, project=p, name=buildName, 156 # We use get_possibly_private_object() here just for consistency as it's
157 # not really needed because the above will fail if the user doesn't have
158 # the rights to see this build.
159 b = get_possibly_private_object(
160 user, BuildResult, project=p, name=buildName,
140 result=ProjectBuildStates.SUCCESS, release=None)161 result=ProjectBuildStates.SUCCESS, release=None)
141 if request.user.has_perm('queuemanager.add_release'):162 if user.has_perm('queuemanager.add_release'):
142 r = Release()163 r = Release()
143 r.build = b164 r.build = b
144 r.creator = request.user165 r.creator = user
145 if request.method == 'POST':166 if request.method == 'POST':
146 f = ReleaseForm(request.POST, instance=r)167 f = ReleaseForm(request.POST, instance=r)
147 if f.is_valid():168 if f.is_valid():
@@ -175,21 +196,35 @@
175project_create = permission_required('queuemanager.add_project')(project_create)196project_create = permission_required('queuemanager.add_project')(project_create)
176197
177def project_edit(request, projectName):198def project_edit(request, projectName):
178 return update_object(199 # XXX: This nasty hack is because update_object uses (via lookup_object)
179 request,200 # .objects directly when it should insted use the default manager
180 form_class=EditProjectForm,201 # (https://code.djangoproject.com/ticket/17021).
181 login_required=True,202 from django.views.generic import create_update
182 template_name='queuemanager/project_edit.html',203 orig_lookup = create_update.lookup_object
183 template_object_name='project',204 def lookup(model, obj_id, slug, slug_field):
184 extra_context={ 'pillar' : 'projects' },205 return get_possibly_private_object(
185 object_id=projectName,206 request.user, Project, pk=projectName)
186 )207 create_update.lookup_object = lookup
208 try:
209 response = update_object(
210 request,
211 form_class=EditProjectForm,
212 login_required=True,
213 template_name='queuemanager/project_edit.html',
214 template_object_name='project',
215 extra_context={ 'pillar' : 'projects' },
216 object_id=projectName,
217 )
218 finally:
219 create_update.lookup_object = orig_lookup
220 return response
187project_edit = permission_required('queuemanager.change_project')(project_edit)221project_edit = permission_required('queuemanager.change_project')(project_edit)
188222
189def project_details(request, projectName):223def project_details(request, projectName):
190 p = get_object_or_404(224 user_visible_objects = Project.all_objects.accessible_by_user(
191 Project.objects.select_related('project_group', 'launchpad_project'), 225 request.user)
192 pk=projectName)226 p = get_possibly_private_object(
227 request.user, user_visible_objects, pk=projectName)
193 project_build_results = BuildResult.objects.filter(228 project_build_results = BuildResult.objects.filter(
194 project = p).exclude(result = None)229 project = p).exclude(result = None)
195 build_stats = { 230 build_stats = {

Subscribers

People subscribed via source and target branches