Merge lp:~rvb/maas/release-security into lp:~maas-committers/maas/trunk
- release-security
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Maintainers | Pending | ||
Review via email: mp+206970@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
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_commissio
ning() 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 |
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