Merge lp:~rvb/maas/release-security into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~rvb/maas/release-security
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 462 lines (+53/-103)
5 files modified
src/maasserver/api.py (+11/-9)
src/maasserver/models/node.py (+9/-13)
src/maasserver/models/tests/test_node.py (+25/-77)
src/maasserver/node_action.py (+2/-4)
src/maasserver/tests/test_node_action.py (+6/-0)
To merge this branch: bzr merge lp:~rvb/maas/release-security
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+206970@code.launchpad.net
To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

Transitioned to Git.

lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.

git clone https://git.launchpad.net/maas

Unmerged revisions

1974. By Raphaël Badin

Add test.

1973. By Raphaël Badin

Fix tests..

1972. By Raphaël Badin

Fix api.stop().

1971. By Raphaël Badin

Fix Node.release().

1970. By Raphaël Badin

Refactor start_commissioning() and accept_enlistment().

1969. By Raphaël Badin

Remove redundant permission check.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2014-02-18 03:19:42 +0000
3+++ src/maasserver/api.py 2014-02-18 15:45:33 +0000
4@@ -375,7 +375,10 @@
5 @operation(idempotent=False)
6 def stop(self, request, system_id):
7 """Shut down a node."""
8- nodes = Node.objects.stop_nodes([system_id], request.user)
9+ node = Node.objects.get_node_or_404(
10+ system_id=system_id, user=request.user,
11+ perm=NODE_PERMISSION.EDIT)
12+ nodes = Node.objects.stop_nodes([node.system_id])
13 if len(nodes) == 0:
14 raise PermissionDenied(
15 "You are not allowed to shut down this node.")
16@@ -401,13 +404,12 @@
17 series = request.POST.get('distro_series', None)
18 if user_data is not None:
19 user_data = b64decode(user_data)
20+ node = Node.objects.get_node_or_404(
21+ system_id=system_id, user=request.user,
22+ perm=NODE_PERMISSION.EDIT)
23 if series is not None:
24- node = Node.objects.get_node_or_404(
25- system_id=system_id, user=request.user,
26- perm=NODE_PERMISSION.EDIT)
27 node.set_distro_series(series=series)
28- nodes = Node.objects.start_nodes(
29- [system_id], request.user, user_data=user_data)
30+ nodes = Node.objects.start_nodes([system_id], user_data=user_data)
31 if len(nodes) == 0:
32 raise PermissionDenied(
33 "You are not allowed to start up this node.")
34@@ -648,7 +650,7 @@
35 """
36 node = create_node(request)
37 if request.user.is_superuser:
38- node.accept_enlistment(request.user)
39+ node.accept_enlistment()
40 return node
41
42 def _check_system_ids_exist(self, system_ids):
43@@ -699,7 +701,7 @@
44 "following node(s): %s." % (
45 ', '.join(system_ids - permitted_ids)))
46 return filter(
47- None, [node.accept_enlistment(request.user) for node in nodes])
48+ None, [node.accept_enlistment() for node in nodes])
49
50 @operation(idempotent=False)
51 def accept_all(self, request):
52@@ -717,7 +719,7 @@
53 nodes = Node.objects.get_nodes(
54 request.user, perm=NODE_PERMISSION.ADMIN)
55 nodes = nodes.filter(status=NODE_STATUS.DECLARED)
56- nodes = [node.accept_enlistment(request.user) for node in nodes]
57+ nodes = [node.accept_enlistment() for node in nodes]
58 return filter(None, nodes)
59
60 @operation(idempotent=False)
61
62=== modified file 'src/maasserver/models/node.py'
63--- src/maasserver/models/node.py 2014-02-10 06:24:38 +0000
64+++ src/maasserver/models/node.py 2014-02-18 15:45:33 +0000
65@@ -287,7 +287,7 @@
66 available_nodes = self.get_nodes(for_user, NODE_PERMISSION.VIEW)
67 return available_nodes.filter(status=NODE_STATUS.READY)
68
69- def stop_nodes(self, ids, by_user):
70+ def stop_nodes(self, ids):
71 """Request on given user's behalf that the given nodes be shut down.
72
73 Shutdown is only requested for nodes that the user has ownership
74@@ -295,12 +295,10 @@
75
76 :param ids: The `system_id` values for nodes to be shut down.
77 :type ids: Sequence
78- :param by_user: Requesting user.
79- :type by_user: User_
80 :return: Those Nodes for which shutdown was actually requested.
81 :rtype: list
82 """
83- nodes = self.get_nodes(by_user, NODE_PERMISSION.EDIT, ids=ids)
84+ nodes = self.filter_by_ids(Node.objects.all(), ids=ids)
85 processed_nodes = []
86 for node in nodes:
87 power_params = node.get_effective_power_parameters()
88@@ -321,7 +319,7 @@
89 processed_nodes.append(node)
90 return processed_nodes
91
92- def start_nodes(self, ids, by_user, user_data=None):
93+ def start_nodes(self, ids, user_data=None):
94 """Request on given user's behalf that the given nodes be started up.
95
96 Power-on is only requested for nodes that the user has ownership
97@@ -332,8 +330,6 @@
98
99 :param ids: The `system_id` values for nodes to be started.
100 :type ids: Sequence
101- :param by_user: Requesting user.
102- :type by_user: User_
103 :param user_data: Optional blob of user-data to be made available to
104 the nodes through the metadata service. If not given, any
105 previous user data is used.
106@@ -344,7 +340,7 @@
107 # Avoid circular imports.
108 from metadataserver.models import NodeUserData
109
110- nodes = self.get_nodes(by_user, NODE_PERMISSION.EDIT, ids=ids)
111+ nodes = self.filter_by_ids(Node.objects.all(), ids=ids)
112 for node in nodes:
113 NodeUserData.objects.set_user_data(node, user_data)
114 processed_nodes = []
115@@ -617,7 +613,7 @@
116 if mac:
117 mac.delete()
118
119- def accept_enlistment(self, user):
120+ def accept_enlistment(self):
121 """Accept this node's (anonymous) enlistment.
122
123 This call makes sense only on a node in Declared state, i.e. one that
124@@ -637,10 +633,10 @@
125 "Cannot accept node enlistment: node %s is in state %s."
126 % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status]))
127
128- self.start_commissioning(user)
129+ self.start_commissioning()
130 return self
131
132- def start_commissioning(self, user):
133+ def start_commissioning(self):
134 """Install OS and self-test a new node."""
135 # Avoid circular imports.
136 from metadataserver.commissioning.user_data import generate_user_data
137@@ -652,7 +648,7 @@
138 self.save()
139 # The commissioning profile is handled in start_nodes.
140 Node.objects.start_nodes(
141- [self.system_id], user, user_data=commissioning_user_data)
142+ [self.system_id], user_data=commissioning_user_data)
143
144 def delete(self):
145 # Allocated nodes can't be deleted.
146@@ -804,7 +800,7 @@
147
148 def release(self):
149 """Mark allocated or reserved node as available again and power off."""
150- Node.objects.stop_nodes([self.system_id], self.owner)
151+ Node.objects.stop_nodes([self.system_id])
152 self.status = NODE_STATUS.READY
153 self.owner = None
154 self.token = None
155
156=== modified file 'src/maasserver/models/tests/test_node.py'
157--- src/maasserver/models/tests/test_node.py 2014-02-14 03:44:32 +0000
158+++ src/maasserver/models/tests/test_node.py 2014-02-18 15:45:33 +0000
159@@ -289,7 +289,6 @@
160 # through the motions right up to the point where we'd normally
161 # run shell commands.
162 self.patch(PowerAction, 'run_shell', lambda *args, **kwargs: ('', ''))
163- user = factory.make_admin()
164 nodes = [
165 factory.make_node(power_type=power_type)
166 for power_type in configless_power_types]
167@@ -299,7 +298,7 @@
168 node: node.get_effective_power_type()
169 for node in nodes}
170 started_nodes = Node.objects.start_nodes(
171- [node.system_id for node in list(node_power_types.keys())], user)
172+ [node.system_id for node in list(node_power_types.keys())])
173 successful_types = [node_power_types[node] for node in started_nodes]
174 self.assertItemsEqual(configless_power_types, successful_types)
175
176@@ -489,7 +488,7 @@
177 target_state = NODE_STATUS.COMMISSIONING
178
179 node = factory.make_node(status=NODE_STATUS.DECLARED)
180- return_value = node.accept_enlistment(factory.make_user())
181+ return_value = node.accept_enlistment()
182 self.assertEqual((node, target_state), (return_value, node.status))
183
184 def test_accept_enlistment_does_nothing_if_already_accepted(self):
185@@ -505,7 +504,7 @@
186 for status in accepted_states}
187
188 return_values = {
189- status: node.accept_enlistment(factory.make_user())
190+ status: node.accept_enlistment()
191 for status, node in nodes.items()}
192
193 self.assertEqual(
194@@ -533,7 +532,7 @@
195 exceptions = {status: False for status in unacceptable_states}
196 for status, node in nodes.items():
197 try:
198- node.accept_enlistment(factory.make_user())
199+ node.accept_enlistment()
200 except NodeStateViolation:
201 exceptions[status] = True
202
203@@ -547,7 +546,7 @@
204 node = factory.make_node(
205 status=NODE_STATUS.DECLARED, power_type='ether_wake')
206 factory.make_mac_address(node=node)
207- node.start_commissioning(factory.make_admin())
208+ node.start_commissioning()
209
210 expected_attrs = {
211 'status': NODE_STATUS.COMMISSIONING,
212@@ -557,25 +556,13 @@
213 ['provisioningserver.tasks.power_on'],
214 [task['task'].name for task in self.celery.tasks])
215
216- def test_start_commisssioning_doesnt_start_nodes_for_non_admin_users(self):
217- node = factory.make_node(
218- status=NODE_STATUS.DECLARED, power_type='ether_wake')
219- factory.make_mac_address(node=node)
220- node.start_commissioning(factory.make_user())
221-
222- expected_attrs = {
223- 'status': NODE_STATUS.COMMISSIONING,
224- }
225- self.assertAttributes(node, expected_attrs)
226- self.assertEqual([], self.celery.tasks)
227-
228 def test_start_commissioning_sets_user_data(self):
229 node = factory.make_node(status=NODE_STATUS.DECLARED)
230 user_data = factory.getRandomString().encode('ascii')
231 self.patch(
232 commissioning.user_data, 'generate_user_data'
233 ).return_value = user_data
234- node.start_commissioning(factory.make_admin())
235+ node.start_commissioning()
236 self.assertEqual(user_data, NodeUserData.objects.get_user_data(node))
237 commissioning.user_data.generate_user_data.assert_called_with(
238 nodegroup=node.nodegroup)
239@@ -586,7 +573,7 @@
240 node, factory.getRandomString(),
241 random.randint(0, 10),
242 Bin(factory.getRandomBytes()))
243- node.start_commissioning(factory.make_admin())
244+ node.start_commissioning()
245 self.assertItemsEqual([], node.nodecommissionresult_set.all())
246
247 def test_start_commissioning_ignores_other_commissioning_results(self):
248@@ -597,7 +584,7 @@
249 NodeCommissionResult.objects.store_data(
250 node, filename, script_result, Bin(data))
251 other_node = factory.make_node(status=NODE_STATUS.DECLARED)
252- other_node.start_commissioning(factory.make_admin())
253+ other_node.start_commissioning()
254 self.assertEqual(
255 data, NodeCommissionResult.objects.get_data(node, filename))
256
257@@ -987,7 +974,7 @@
258 self.patch(PowerAction, 'run_shell', lambda *args, **kwargs: ('', ''))
259 user = factory.make_user()
260 node, mac = self.make_node_with_mac(user, power_type='virsh')
261- output = Node.objects.stop_nodes([node.system_id], user)
262+ output = Node.objects.stop_nodes([node.system_id])
263
264 self.assertItemsEqual([node], output)
265 self.assertEqual(
266@@ -1001,21 +988,10 @@
267 user = factory.make_user()
268 node, mac = self.make_node_with_mac(user, power_type='virsh')
269 task = self.patch(node_module, 'power_off')
270- Node.objects.stop_nodes([node.system_id], user)
271+ Node.objects.stop_nodes([node.system_id])
272 args, kwargs = task.apply_async.call_args
273 self.assertEqual(node.work_queue, kwargs['queue'])
274
275- def test_stop_nodes_ignores_uneditable_nodes(self):
276- nodes = [
277- self.make_node_with_mac(
278- factory.make_user(), power_type='ether_wake')
279- for counter in range(3)]
280- ids = [node.system_id for node, mac in nodes]
281- stoppable_node = nodes[0][0]
282- self.assertItemsEqual(
283- [stoppable_node],
284- Node.objects.stop_nodes(ids, stoppable_node.owner))
285-
286 def test_stop_nodes_does_not_attempt_power_task_if_no_power_type(self):
287 # If the node has a power_type set to UNKNOWN_POWER_TYPE
288 # NodeManager.stop_node(this_node) won't create a power event
289@@ -1023,7 +999,7 @@
290 user = factory.make_user()
291 node, unused = self.make_node_with_mac(
292 user, power_type='')
293- output = Node.objects.stop_nodes([node.system_id], user)
294+ output = Node.objects.stop_nodes([node.system_id])
295
296 self.assertItemsEqual([], output)
297 self.assertEqual(0, len(self.celery.tasks))
298@@ -1032,7 +1008,7 @@
299 user = factory.make_user()
300 node, mac = self.make_node_with_mac(
301 user, power_type='ether_wake')
302- output = Node.objects.start_nodes([node.system_id], user)
303+ output = Node.objects.start_nodes([node.system_id])
304
305 self.assertItemsEqual([node], output)
306 self.assertEqual(
307@@ -1048,7 +1024,7 @@
308 node, mac = self.make_node_with_mac(
309 user, power_type='ether_wake')
310 task = self.patch(node_module, 'power_on')
311- Node.objects.start_nodes([node.system_id], user)
312+ Node.objects.start_nodes([node.system_id])
313 args, kwargs = task.apply_async.call_args
314 self.assertEqual(node.work_queue, kwargs['queue'])
315
316@@ -1059,7 +1035,7 @@
317 user = factory.make_user()
318 node, unused = self.make_node_with_mac(
319 user, power_type='')
320- output = Node.objects.start_nodes([node.system_id], user)
321+ output = Node.objects.start_nodes([node.system_id])
322
323 self.assertItemsEqual([], output)
324 self.assertEqual(0, len(self.celery.tasks))
325@@ -1067,12 +1043,11 @@
326 def test_start_nodes_wakeonlan_prefers_power_parameters(self):
327 # If power_parameters is set we should prefer it to sifting
328 # through related MAC addresses.
329- user = factory.make_user()
330 preferred_mac = factory.getRandomMACAddress()
331 node, mac = self.make_node_with_mac(
332- user, power_type='ether_wake',
333+ power_type='ether_wake',
334 power_parameters=dict(mac_address=preferred_mac))
335- output = Node.objects.start_nodes([node.system_id], user)
336+ output = Node.objects.start_nodes([node.system_id])
337
338 self.assertItemsEqual([node], output)
339 self.assertEqual(
340@@ -1086,64 +1061,38 @@
341 def test_start_nodes_wakeonlan_ignores_invalid_parameters(self):
342 # If node.power_params is set but doesn't have "mac_address" in it,
343 # then the node shouldn't be started.
344- user = factory.make_user()
345 node, mac = self.make_node_with_mac(
346- user, power_type='ether_wake',
347+ power_type='ether_wake',
348 power_parameters=dict(jarjar="binks"))
349- output = Node.objects.start_nodes([node.system_id], user)
350+ output = Node.objects.start_nodes([node.system_id])
351 self.assertItemsEqual([], output)
352 self.assertEqual([], self.celery.tasks)
353
354 def test_start_nodes_wakeonlan_ignores_empty_mac_address_parameter(self):
355- user = factory.make_user()
356 node, mac = self.make_node_with_mac(
357- user, power_type='ether_wake',
358+ power_type='ether_wake',
359 power_parameters=dict(mac_address=""))
360- output = Node.objects.start_nodes([node.system_id], user)
361+ output = Node.objects.start_nodes([node.system_id])
362 self.assertItemsEqual([], output)
363 self.assertEqual([], self.celery.tasks)
364
365 def test_start_nodes_ignores_nodes_without_mac(self):
366- user = factory.make_user()
367- node = self.make_node(user)
368- output = Node.objects.start_nodes([node.system_id], user)
369+ node = self.make_node()
370+ output = Node.objects.start_nodes([node.system_id])
371
372 self.assertItemsEqual([], output)
373
374- def test_start_nodes_ignores_uneditable_nodes(self):
375- nodes = [
376- self.make_node_with_mac(
377- factory.make_user(), power_type='ether_wake')[0]
378- for counter in range(3)
379- ]
380- ids = [node.system_id for node in nodes]
381- startable_node = nodes[0]
382- self.assertItemsEqual(
383- [startable_node],
384- Node.objects.start_nodes(ids, startable_node.owner))
385-
386 def test_start_nodes_stores_user_data(self):
387 node = factory.make_node(owner=factory.make_user())
388 user_data = self.make_user_data()
389- Node.objects.start_nodes(
390- [node.system_id], node.owner, user_data=user_data)
391+ Node.objects.start_nodes([node.system_id], user_data=user_data)
392 self.assertEqual(user_data, NodeUserData.objects.get_user_data(node))
393
394- def test_start_nodes_does_not_store_user_data_for_uneditable_nodes(self):
395- node = factory.make_node(owner=factory.make_user())
396- original_user_data = self.make_user_data()
397- NodeUserData.objects.set_user_data(node, original_user_data)
398- Node.objects.start_nodes(
399- [node.system_id], factory.make_user(),
400- user_data=self.make_user_data())
401- self.assertEqual(
402- original_user_data, NodeUserData.objects.get_user_data(node))
403-
404 def test_start_nodes_without_user_data_clears_existing_data(self):
405 node = factory.make_node(owner=factory.make_user())
406 user_data = self.make_user_data()
407 NodeUserData.objects.set_user_data(node, user_data)
408- Node.objects.start_nodes([node.system_id], node.owner, user_data=None)
409+ Node.objects.start_nodes([node.system_id], user_data=None)
410 self.assertRaises(
411 NodeUserData.DoesNotExist,
412 NodeUserData.objects.get_user_data, node)
413@@ -1152,8 +1101,7 @@
414 node = factory.make_node(owner=factory.make_user())
415 NodeUserData.objects.set_user_data(node, self.make_user_data())
416 user_data = self.make_user_data()
417- Node.objects.start_nodes(
418- [node.system_id], node.owner, user_data=user_data)
419+ Node.objects.start_nodes([node.system_id], user_data=user_data)
420 self.assertEqual(user_data, NodeUserData.objects.get_user_data(node))
421
422 def test_netboot_on(self):
423
424=== modified file 'src/maasserver/node_action.py'
425--- src/maasserver/node_action.py 2013-11-15 18:15:51 +0000
426+++ src/maasserver/node_action.py 2014-02-18 15:45:33 +0000
427@@ -177,7 +177,7 @@
428
429 def execute(self, allow_redirect=True):
430 """See `NodeAction.execute`."""
431- self.node.start_commissioning(self.user)
432+ self.node.start_commissioning()
433 return "Node commissioning started."
434
435
436@@ -242,9 +242,7 @@
437 # acquire() call.
438 self.node.acquire(self.user, token=None)
439
440- # Be sure to acquire before starting, or start_nodes will think
441- # the node ineligible based on its un-acquired status.
442- Node.objects.start_nodes([self.node.system_id], self.user)
443+ Node.objects.start_nodes([self.node.system_id])
444 return dedent("""\
445 This node is now allocated to you.
446 It has been asked to start up.
447
448=== modified file 'src/maasserver/tests/test_node_action.py'
449--- src/maasserver/tests/test_node_action.py 2014-01-14 00:43:21 +0000
450+++ src/maasserver/tests/test_node_action.py 2014-02-18 15:45:33 +0000
451@@ -263,6 +263,12 @@
452 'provisioningserver.tasks.power_off',
453 self.celery.tasks[0]['task'].name)
454
455+ def test_StopNode_is_not_permitted_to_non_owner(self):
456+ node = factory.make_node(
457+ mac=True, status=NODE_STATUS.ALLOCATED,
458+ power_type='ether_wake', owner=factory.make_user())
459+ self.assertFalse(StopNode(node, factory.make_user()).is_permitted())
460+
461
462 class TestUseCurtinNodeAction(MAASServerTestCase):
463