Merge lp:~trapnine/maas/fix-1576417-controllers-admin-only into lp:~maas-committers/maas/trunk

Proposed by Jeffrey C Jones
Status: Merged
Approved by: Jeffrey C Jones
Approved revision: no longer in the source branch.
Merged at revision: 5027
Proposed branch: lp:~trapnine/maas/fix-1576417-controllers-admin-only
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 357 lines (+135/-12)
11 files modified
src/maasserver/api/rackcontrollers.py (+2/-0)
src/maasserver/api/tests/test_events.py (+2/-0)
src/maasserver/api/tests/test_rackcontroller.py (+33/-0)
src/maasserver/api/tests/test_regioncontroller.py (+1/-0)
src/maasserver/api/tests/test_tag.py (+42/-6)
src/maasserver/models/node.py (+8/-1)
src/maasserver/models/tests/test_node.py (+21/-0)
src/maasserver/static/js/angular/controllers/nodes_list.js (+5/-0)
src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js (+16/-0)
src/maasserver/static/partials/nodes-list.html (+2/-2)
src/maasserver/websockets/handlers/tests/test_controller.py (+3/-3)
To merge this branch: bzr merge lp:~trapnine/maas/fix-1576417-controllers-admin-only
Reviewer Review Type Date Requested Status
Jeffrey C Jones (community) Approve
Gavin Panella (community) Approve
Review via email: mp+294785@code.launchpad.net

Commit message

Non-admins can't see controllers.

Description of the change

Non-admins can't see controllers.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, except that isSuperUser needs a simple test.

review: Approve
Revision history for this message
Jeffrey C Jones (trapnine) wrote :

Approved by Gavin.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/rackcontrollers.py'
--- src/maasserver/api/rackcontrollers.py 2016-04-28 01:11:50 +0000
+++ src/maasserver/api/rackcontrollers.py 2016-05-16 16:20:02 +0000
@@ -154,6 +154,7 @@
154 "Import of boot images started on %s" % rack.hostname,154 "Import of boot images started on %s" % rack.hostname,
155 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))155 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
156156
157 @admin_method
157 @operation(idempotent=True)158 @operation(idempotent=True)
158 def list_boot_images(self, request, system_id):159 def list_boot_images(self, request, system_id):
159 """List all available boot images.160 """List all available boot images.
@@ -192,6 +193,7 @@
192 "Import of boot images started on all rack controllers",193 "Import of boot images started on all rack controllers",
193 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))194 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
194195
196 @admin_method
195 @operation(idempotent=True)197 @operation(idempotent=True)
196 def describe_power_types(self, request):198 def describe_power_types(self, request):
197 """Query all of the rack controllers for power information.199 """Query all of the rack controllers for power information.
198200
=== modified file 'src/maasserver/api/tests/test_events.py'
--- src/maasserver/api/tests/test_events.py 2016-02-02 14:20:45 +0000
+++ src/maasserver/api/tests/test_events.py 2016-05-16 16:20:02 +0000
@@ -143,6 +143,7 @@
143 # Even when node ids are passed to "list," events for nodes are143 # Even when node ids are passed to "list," events for nodes are
144 # returned in event id order, not necessarily in the order of the144 # returned in event id order, not necessarily in the order of the
145 # node id arguments.145 # node id arguments.
146 self.become_admin()
146 nodes = [factory.make_Node() for _ in range(3)]147 nodes = [factory.make_Node() for _ in range(3)]
147 events = [factory.make_Event(node=node) for node in nodes]148 events = [factory.make_Event(node=node) for node in nodes]
148 response = self.client.get(149 response = self.client.get(
@@ -310,6 +311,7 @@
310 self.assertEqual(parsed_result['count'], len(events))311 self.assertEqual(parsed_result['count'], len(events))
311312
312 def test_GET_query_doesnt_list_devices(self):313 def test_GET_query_doesnt_list_devices(self):
314 self.become_admin()
313 machines = [315 machines = [
314 factory.make_Node(316 factory.make_Node(
315 agent_name=factory.make_name('agent-name'),317 agent_name=factory.make_name('agent-name'),
316318
=== modified file 'src/maasserver/api/tests/test_rackcontroller.py'
--- src/maasserver/api/tests/test_rackcontroller.py 2016-04-15 22:14:33 +0000
+++ src/maasserver/api/tests/test_rackcontroller.py 2016-05-16 16:20:02 +0000
@@ -6,6 +6,7 @@
6import http.client6import http.client
77
8from django.core.urlresolvers import reverse8from django.core.urlresolvers import reverse
9from maasserver.api import rackcontrollers
9from maasserver.models import node as node_module10from maasserver.models import node as node_module
10from maasserver.testing.api import (11from maasserver.testing.api import (
11 APITestCase,12 APITestCase,
@@ -17,6 +18,7 @@
17from maastesting.matchers import (18from maastesting.matchers import (
18 MockCalledOnce,19 MockCalledOnce,
19 MockCalledOnceWith,20 MockCalledOnceWith,
21 MockNotCalled,
20)22)
2123
2224
@@ -87,6 +89,7 @@
8789
88 def test_GET_list_boot_images(self):90 def test_GET_list_boot_images(self):
89 rack = factory.make_RackController(owner=factory.make_User())91 rack = factory.make_RackController(owner=factory.make_User())
92 self.become_admin()
90 response = self.client.get(93 response = self.client.get(
91 self.get_rack_uri(rack), {'op': 'list_boot_images'})94 self.get_rack_uri(rack), {'op': 'list_boot_images'})
92 self.assertEqual(95 self.assertEqual(
@@ -96,6 +99,14 @@
96 ['connected', 'images', 'status'],99 ['connected', 'images', 'status'],
97 json_load_bytes(response.content))100 json_load_bytes(response.content))
98101
102 def test_GET_list_boot_images_denied_if_not_admin(self):
103 rack = factory.make_RackController(owner=factory.make_User())
104 response = self.client.get(
105 self.get_rack_uri(rack), {'op': 'list_boot_images'})
106 self.assertEqual(
107 http.client.FORBIDDEN, response.status_code,
108 explain_unexpected_response(http.client.FORBIDDEN, response))
109
99110
100class TestRackControllersAPI(APITestCase):111class TestRackControllersAPI(APITestCase):
101 """Tests for /api/2.0/rackcontrollers/."""112 """Tests for /api/2.0/rackcontrollers/."""
@@ -110,6 +121,7 @@
110 '/api/2.0/rackcontrollers/', reverse('rackcontrollers_handler'))121 '/api/2.0/rackcontrollers/', reverse('rackcontrollers_handler'))
111122
112 def test_read_returns_limited_fields(self):123 def test_read_returns_limited_fields(self):
124 self.become_admin()
113 factory.make_RackController(owner=self.logged_in_user)125 factory.make_RackController(owner=self.logged_in_user)
114 response = self.client.get(reverse('rackcontrollers_handler'))126 response = self.client.get(reverse('rackcontrollers_handler'))
115 parsed_result = json_load_bytes(response.content)127 parsed_result = json_load_bytes(response.content)
@@ -159,3 +171,24 @@
159 self.assertEqual(171 self.assertEqual(
160 http.client.FORBIDDEN, response.status_code,172 http.client.FORBIDDEN, response.status_code,
161 explain_unexpected_response(http.client.FORBIDDEN, response))173 explain_unexpected_response(http.client.FORBIDDEN, response))
174
175 def test_GET_describe_power_types(self):
176 get_all_power_types_from_clusters = self.patch(
177 rackcontrollers, "get_all_power_types_from_clusters")
178 self.become_admin()
179 response = self.client.get(
180 self.get_rack_uri(), {'op': 'describe_power_types'})
181 self.assertEqual(
182 http.client.OK, response.status_code,
183 explain_unexpected_response(http.client.OK, response))
184 self.assertThat(get_all_power_types_from_clusters, MockCalledOnce())
185
186 def test_GET_describe_power_types_denied_if_not_admin(self):
187 get_all_power_types_from_clusters = self.patch(
188 rackcontrollers, "get_all_power_types_from_clusters")
189 response = self.client.get(
190 self.get_rack_uri(), {'op': 'describe_power_types'})
191 self.assertEqual(
192 http.client.FORBIDDEN, response.status_code,
193 explain_unexpected_response(http.client.FORBIDDEN, response))
194 self.assertThat(get_all_power_types_from_clusters, MockNotCalled())
162195
=== modified file 'src/maasserver/api/tests/test_regioncontroller.py'
--- src/maasserver/api/tests/test_regioncontroller.py 2016-04-13 02:20:16 +0000
+++ src/maasserver/api/tests/test_regioncontroller.py 2016-05-16 16:20:02 +0000
@@ -54,6 +54,7 @@
54 reverse('regioncontrollers_handler'))54 reverse('regioncontrollers_handler'))
5555
56 def test_read_returns_limited_fields(self):56 def test_read_returns_limited_fields(self):
57 self.become_admin()
57 factory.make_RegionController()58 factory.make_RegionController()
58 response = self.client.get(reverse('regioncontrollers_handler'))59 response = self.client.get(reverse('regioncontrollers_handler'))
59 parsed_result = json_load_bytes(response.content)60 parsed_result = json_load_bytes(response.content)
6061
=== modified file 'src/maasserver/api/tests/test_tag.py'
--- src/maasserver/api/tests/test_tag.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/api/tests/test_tag.py 2016-05-16 16:20:02 +0000
@@ -147,11 +147,8 @@
147 self.assertEqual(http.client.OK, response.status_code)147 self.assertEqual(http.client.OK, response.status_code)
148 parsed_result = json.loads(148 parsed_result = json.loads(
149 response.content.decode(settings.DEFAULT_CHARSET))149 response.content.decode(settings.DEFAULT_CHARSET))
150 # XXX lamont Bug#1576417 : Region controllers should not be here
151 # either, but are the subject of said bug.
152 self.assertItemsEqual(150 self.assertItemsEqual(
153 [machine.system_id, device.system_id,151 [machine.system_id, device.system_id],
154 region.system_id],
155 [r['system_id'] for r in parsed_result])152 [r['system_id'] for r in parsed_result])
156153
157 def test_GET_machines_returns_machines(self):154 def test_GET_machines_returns_machines(self):
@@ -196,8 +193,6 @@
196 [device.system_id],193 [device.system_id],
197 [r['system_id'] for r in parsed_result])194 [r['system_id'] for r in parsed_result])
198195
199 # XXX lamont Bug#1576417: Add a test that non-admins do not get to fetch
200 # rack controllers.
201 def test_GET_rack_controllers_returns_rack_controllers(self):196 def test_GET_rack_controllers_returns_rack_controllers(self):
202 self.become_admin()197 self.become_admin()
203 tag = factory.make_Tag()198 tag = factory.make_Tag()
@@ -221,7 +216,28 @@
221 [rack.system_id],216 [rack.system_id],
222 [r['system_id'] for r in parsed_result])217 [r['system_id'] for r in parsed_result])
223218
219 def test_GET_rack_controllers_returns_no_rack_controllers_nonadmin(self):
220 tag = factory.make_Tag()
221 machine = factory.make_Node()
222 device = factory.make_Device()
223 rack = factory.make_RackController()
224 region = factory.make_RegionController()
225 # Create a second node that isn't tagged.
226 factory.make_Node()
227 machine.tags.add(tag)
228 device.tags.add(tag)
229 rack.tags.add(tag)
230 region.tags.add(tag)
231 response = self.client.get(
232 self.get_tag_uri(tag), {'op': 'rack_controllers'})
233
234 self.assertEqual(http.client.OK, response.status_code)
235 parsed_result = json.loads(
236 response.content.decode(settings.DEFAULT_CHARSET))
237 self.assertItemsEqual([], parsed_result)
238
224 def test_GET_region_controllers_returns_region_controllers(self):239 def test_GET_region_controllers_returns_region_controllers(self):
240 self.become_admin()
225 tag = factory.make_Tag()241 tag = factory.make_Tag()
226 machine = factory.make_Node()242 machine = factory.make_Node()
227 device = factory.make_Device()243 device = factory.make_Device()
@@ -243,6 +259,26 @@
243 [region.system_id],259 [region.system_id],
244 [r['system_id'] for r in parsed_result])260 [r['system_id'] for r in parsed_result])
245261
262 def test_GET_region_controllers_returns_no_controllers_nonadmin(self):
263 tag = factory.make_Tag()
264 machine = factory.make_Node()
265 device = factory.make_Device()
266 rack = factory.make_RackController()
267 region = factory.make_RegionController()
268 # Create a second node that isn't tagged.
269 factory.make_Node()
270 machine.tags.add(tag)
271 device.tags.add(tag)
272 rack.tags.add(tag)
273 region.tags.add(tag)
274 response = self.client.get(
275 self.get_tag_uri(tag), {'op': 'region_controllers'})
276
277 self.assertEqual(http.client.OK, response.status_code)
278 parsed_result = json.loads(
279 response.content.decode(settings.DEFAULT_CHARSET))
280 self.assertItemsEqual([], parsed_result)
281
246 def test_GET_nodes_hides_invisible_nodes(self):282 def test_GET_nodes_hides_invisible_nodes(self):
247 user2 = factory.make_User()283 user2 = factory.make_User()
248 node1 = factory.make_Node()284 node1 = factory.make_Node()
249285
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/models/node.py 2016-05-16 16:20:02 +0000
@@ -409,7 +409,14 @@
409 if user.is_superuser:409 if user.is_superuser:
410 # Admin is allowed to see all nodes.410 # Admin is allowed to see all nodes.
411 return nodes411 return nodes
412 elif perm == NODE_PERMISSION.VIEW:412 # Non-admins aren't allowed to see controllers.
413 nodes = nodes.exclude(
414 Q(node_type__in=[
415 NODE_TYPE.RACK_CONTROLLER,
416 NODE_TYPE.REGION_CONTROLLER,
417 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
418 ]))
419 if perm == NODE_PERMISSION.VIEW:
413 return nodes.filter(Q(owner__isnull=True) | Q(owner=user))420 return nodes.filter(Q(owner__isnull=True) | Q(owner=user))
414 elif perm == NODE_PERMISSION.EDIT:421 elif perm == NODE_PERMISSION.EDIT:
415 return nodes.filter(owner=user)422 return nodes.filter(owner=user)
416423
=== modified file 'src/maasserver/models/tests/test_node.py'
--- src/maasserver/models/tests/test_node.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/models/tests/test_node.py 2016-05-16 16:20:02 +0000
@@ -3255,6 +3255,9 @@
3255 def test_get_nodes_with_admin_perm_returns_all_nodes_for_admin(self):3255 def test_get_nodes_with_admin_perm_returns_all_nodes_for_admin(self):
3256 user = factory.make_User()3256 user = factory.make_User()
3257 nodes = [self.make_node(user) for counter in range(5)]3257 nodes = [self.make_node(user) for counter in range(5)]
3258 nodes.append(factory.make_RackController())
3259 nodes.append(factory.make_RegionController())
3260 nodes.append(factory.make_RegionRackController())
3258 self.assertItemsEqual(3261 self.assertItemsEqual(
3259 nodes,3262 nodes,
3260 Node.objects.get_nodes(3263 Node.objects.get_nodes(
@@ -3282,6 +3285,24 @@
3282 from_nodes=Node.objects.all())3285 from_nodes=Node.objects.all())
3283 )3286 )
32843287
3288 def test_get_nodes_non_admin_hides_controllers(self):
3289 user = factory.make_User()
3290 user_visible_nodes = [self.make_node(user), self.make_node(None)]
3291 admin_visible_nodes = user_visible_nodes + [
3292 self.make_node(factory.make_User()),
3293 factory.make_RackController(owner=user),
3294 factory.make_RackController(owner=None),
3295 factory.make_RegionController(),
3296 factory.make_RegionRackController(),
3297 ]
3298 self.assertItemsEqual(
3299 admin_visible_nodes,
3300 Node.objects.get_nodes(
3301 factory.make_admin(), NODE_PERMISSION.ADMIN))
3302 self.assertItemsEqual(
3303 user_visible_nodes,
3304 Node.objects.get_nodes(user, NODE_PERMISSION.VIEW))
3305
3285 def test_filter_nodes_by_spaces(self):3306 def test_filter_nodes_by_spaces(self):
3286 # Create a throwaway node and a throwaway space.3307 # Create a throwaway node and a throwaway space.
3287 # (to ensure they are filtered out.)3308 # (to ensure they are filtered out.)
32883309
=== modified file 'src/maasserver/static/js/angular/controllers/nodes_list.js'
--- src/maasserver/static/js/angular/controllers/nodes_list.js 2016-04-29 19:31:59 +0000
+++ src/maasserver/static/js/angular/controllers/nodes_list.js 2016-05-16 16:20:02 +0000
@@ -585,6 +585,11 @@
585 });585 });
586 };586 };
587587
588 // Return true if the authenticated user is super user.
589 $scope.isSuperUser = function() {
590 return UsersManager.isSuperUser();
591 };
592
588 // Load the required managers for this controller. The ServicesManager593 // Load the required managers for this controller. The ServicesManager
589 // is required by the maasControllerStatus directive that is used594 // is required by the maasControllerStatus directive that is used
590 // in the partial for this controller.595 // in the partial for this controller.
591596
=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js'
--- src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2016-04-29 07:41:09 +0000
+++ src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2016-05-16 16:20:02 +0000
@@ -134,6 +134,22 @@
134 return null;134 return null;
135 }135 }
136136
137 describe("isSuperUser", function() {
138 it("returns true if the user is a superuser", function() {
139 var controller = makeController();
140 spyOn(UsersManager, "getAuthUser").and.returnValue(
141 { is_superuser: true });
142 expect($scope.isSuperUser()).toBe(true);
143 });
144
145 it("returns false if the user is not a superuser", function() {
146 var controller = makeController();
147 spyOn(UsersManager, "getAuthUser").and.returnValue(
148 { is_superuser: false });
149 expect($scope.isSuperUser()).toBe(false);
150 });
151 });
152
137 it("sets title and page on $rootScope", function() {153 it("sets title and page on $rootScope", function() {
138 var controller = makeController();154 var controller = makeController();
139 expect($rootScope.title).toBe("Machines");155 expect($rootScope.title).toBe("Machines");
140156
=== modified file 'src/maasserver/static/partials/nodes-list.html'
--- src/maasserver/static/partials/nodes-list.html 2016-05-11 19:01:48 +0000
+++ src/maasserver/static/partials/nodes-list.html 2016-05-16 16:20:02 +0000
@@ -11,8 +11,8 @@
11 data-ng-class="{ active: currentpage === 'devices' }"11 data-ng-class="{ active: currentpage === 'devices' }"
12 data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize>12 data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize>
13 </a>13 </a>
14 <span class="divide"></span>14 <span data-ng-show="isSuperUser()" class="divide"></span>
15 <a href=""15 <a href="" data-ng-show="isSuperUser()"
16 data-ng-class="{ active: currentpage === 'controllers' }"16 data-ng-class="{ active: currentpage === 'controllers' }"
17 data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize>17 data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize>
18 </a>18 </a>
1919
=== modified file 'src/maasserver/websockets/handlers/tests/test_controller.py'
--- src/maasserver/websockets/handlers/tests/test_controller.py 2016-04-22 16:25:08 +0000
+++ src/maasserver/websockets/handlers/tests/test_controller.py 2016-05-16 16:20:02 +0000
@@ -21,7 +21,7 @@
21 factory.make_RackController()21 factory.make_RackController()
2222
23 def test_last_image_sync(self):23 def test_last_image_sync(self):
24 owner = factory.make_User()24 owner = factory.make_admin()
25 handler = ControllerHandler(owner, {})25 handler = ControllerHandler(owner, {})
26 node = factory.make_RackController(owner=owner)26 node = factory.make_RackController(owner=owner)
27 result = handler.list({})27 result = handler.list({})
@@ -34,7 +34,7 @@
34 node.last_image_sync))34 node.last_image_sync))
3535
36 def test_last_image_sync_returns_none_for_none(self):36 def test_last_image_sync_returns_none_for_none(self):
37 owner = factory.make_User()37 owner = factory.make_admin()
38 handler = ControllerHandler(owner, {})38 handler = ControllerHandler(owner, {})
39 node = factory.make_RackController(owner=owner, last_image_sync=None)39 node = factory.make_RackController(owner=owner, last_image_sync=None)
40 result = handler.list({})40 result = handler.list({})
@@ -45,7 +45,7 @@
45 self.assertIsNone(data.get("last_image_sync"))45 self.assertIsNone(data.get("last_image_sync"))
4646
47 def test_list_ignores_devices_and_nodes(self):47 def test_list_ignores_devices_and_nodes(self):
48 owner = factory.make_User()48 owner = factory.make_admin()
49 handler = ControllerHandler(owner, {})49 handler = ControllerHandler(owner, {})
50 # Create a device.50 # Create a device.
51 factory.make_Node(owner=owner, node_type=NODE_TYPE.DEVICE)51 factory.make_Node(owner=owner, node_type=NODE_TYPE.DEVICE)