Merge lp:~blake-rouse/maas/fix-1580260 into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 5023
Proposed branch: lp:~blake-rouse/maas/fix-1580260
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 203 lines (+82/-11)
2 files modified
src/maasserver/api/ipranges.py (+24/-5)
src/maasserver/api/tests/test_ipranges.py (+58/-6)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1580260
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+294564@code.launchpad.net

Commit message

Allow standard users to create reserved IP ranges and update/delete their own ranges.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm! Please improve the messages before landing.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/ipranges.py'
2--- src/maasserver/api/ipranges.py 2016-03-28 13:54:47 +0000
3+++ src/maasserver/api/ipranges.py 2016-05-13 21:37:01 +0000
4@@ -3,11 +3,12 @@
5
6 """API handlers: `ip-ranges`."""
7
8-from maasserver.api.support import (
9- admin_method,
10- OperationsHandler,
11+from maasserver.api.support import OperationsHandler
12+from maasserver.enum import IPRANGE_TYPE
13+from maasserver.exceptions import (
14+ MAASAPIForbidden,
15+ MAASAPIValidationError,
16 )
17-from maasserver.exceptions import MAASAPIValidationError
18 from maasserver.forms_iprange import IPRangeForm
19 from maasserver.models import IPRange
20 from piston3.utils import rc
21@@ -24,6 +25,12 @@
22 )
23
24
25+def raise_error_if_not_owner(iprange, user):
26+ if not user.is_superuser and iprange.user_id != user.id:
27+ raise MAASAPIForbidden(
28+ "Unable to modify IP range. You don't own the IP range.")
29+
30+
31 class IPRangesHandler(OperationsHandler):
32 """Manage IP ranges."""
33 api_doc_section_name = "IP Ranges"
34@@ -39,7 +46,6 @@
35 """List all IP ranges."""
36 return IPRange.objects.all()
37
38- @admin_method
39 def create(self, request):
40 """Create an IP range.
41
42@@ -47,7 +53,16 @@
43 :param start_ip: Start IP address of this range (inclusive).
44 :param end_ip: End IP address of this range (inclusive).
45 :param subnet: Subnet this range is associated with. (optional)
46+
47+ Returns 403 if standard users tries to create a dynamic IP range.
48 """
49+ if ('type' in request.data and
50+ request.data['type'] == IPRANGE_TYPE.DYNAMIC and
51+ not request.user.is_superuser):
52+ raise MAASAPIForbidden(
53+ "Unable to create dynamic IP range. "
54+ "You don't have sufficient privileges.")
55+
56 form = IPRangeForm(data=request.data, request=request)
57 if form.is_valid():
58 return form.save()
59@@ -84,9 +99,11 @@
60 :param start_ip: Start IP address of this range (inclusive).
61 :param end_ip: End IP address of this range (inclusive).
62
63+ Returns 403 if not owner of IP range.
64 Returns 404 if the IP Range is not found.
65 """
66 iprange = IPRange.objects.get_iprange_or_404(iprange_id)
67+ raise_error_if_not_owner(iprange, request.user)
68 form = IPRangeForm(instance=iprange, data=request.data)
69 if form.is_valid():
70 return form.save()
71@@ -96,8 +113,10 @@
72 def delete(self, request, iprange_id):
73 """Delete IP range.
74
75+ Returns 403 if not owner of IP range.
76 Returns 404 if the IP range is not found.
77 """
78 iprange = IPRange.objects.get_iprange_or_404(iprange_id)
79+ raise_error_if_not_owner(iprange, request.user)
80 iprange.delete()
81 return rc.DELETED
82
83=== modified file 'src/maasserver/api/tests/test_ipranges.py'
84--- src/maasserver/api/tests/test_ipranges.py 2016-04-26 19:52:34 +0000
85+++ src/maasserver/api/tests/test_ipranges.py 2016-05-13 21:37:01 +0000
86@@ -59,7 +59,7 @@
87 ]
88 self.assertItemsEqual(expected_ids, result_ids)
89
90- def test_create(self):
91+ def test_create_dynamic(self):
92 self.become_admin()
93 uri = get_ipranges_uri()
94 subnet = factory.make_Subnet(cidr='10.0.0.0/24')
95@@ -76,12 +76,23 @@
96 self.assertThat(data['end_ip'], Equals('10.0.0.20'))
97 self.assertThat(data['subnet']['id'], Equals(subnet.id))
98
99+ def test_create_dynamic_requires_admin(self):
100+ uri = get_ipranges_uri()
101+ subnet = factory.make_Subnet(cidr='10.0.0.0/24')
102+ response = self.client.post(uri, {
103+ "type": "dynamic",
104+ "start_ip": "10.0.0.10",
105+ "end_ip": "10.0.0.20",
106+ "subnet": "%d" % subnet.id,
107+ })
108+ self.assertEqual(
109+ http.client.FORBIDDEN, response.status_code, response.content)
110+
111 def test_create_does_not_require_subnet(self):
112- self.become_admin()
113 uri = get_ipranges_uri()
114 subnet = factory.make_Subnet(cidr='10.0.0.0/24')
115 response = self.client.post(uri, {
116- "type": "dynamic",
117+ "type": "reserved",
118 "start_ip": "10.0.0.10",
119 "end_ip": "10.0.0.20",
120 })
121@@ -93,7 +104,6 @@
122 self.assertThat(data['subnet']['id'], Equals(subnet.id))
123
124 def test_create_requires_type_and_reports_simple_error_if_missing(self):
125- self.become_admin()
126 uri = get_ipranges_uri()
127 factory.make_Subnet(cidr='10.0.0.0/24')
128 response = self.client.post(uri, {
129@@ -105,6 +115,20 @@
130 self.assertThat(response.content, Equals(
131 b'{"type": ["This field is required."]}'))
132
133+ def test_create_sets_user_to_authenticated_user(self):
134+ uri = get_ipranges_uri()
135+ factory.make_Subnet(cidr='10.0.0.0/24')
136+ response = self.client.post(uri, {
137+ "type": "reserved",
138+ "start_ip": "10.0.0.10",
139+ "end_ip": "10.0.0.20",
140+ })
141+ self.assertEqual(
142+ http.client.OK, response.status_code, response.content)
143+ data = json.loads(response.content.decode(settings.DEFAULT_CHARSET))
144+ self.assertThat(
145+ data['user']['username'], Equals(self.logged_in_user.username))
146+
147
148 class TestIPRangeAPI(APITestCase):
149
150@@ -144,7 +168,8 @@
151
152 def test_update(self):
153 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
154- iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
155+ iprange = factory.make_IPRange(
156+ subnet, '10.0.0.2', '10.0.0.10', user=self.logged_in_user)
157 uri = get_iprange_uri(iprange)
158 comment = factory.make_name("comment")
159 response = self.client.put(uri, {
160@@ -158,15 +183,42 @@
161 response.content.decode(settings.DEFAULT_CHARSET))['comment'])
162 self.assertEqual(comment, reload_object(iprange).comment)
163
164+ def test_update_403_when_not_user(self):
165+ subnet = factory.make_Subnet(cidr="10.0.0.0/24")
166+ iprange = factory.make_IPRange(
167+ subnet, '10.0.0.2', '10.0.0.10', user=factory.make_User())
168+ uri = get_iprange_uri(iprange)
169+ response = self.client.put(uri, {})
170+ self.assertEqual(
171+ http.client.FORBIDDEN, response.status_code, response.content)
172+
173+ def test_update_404_when_invalid_id(self):
174+ uri = reverse(
175+ 'iprange_handler', args=[random.randint(100, 1000)])
176+ response = self.client.put(uri, {})
177+ self.assertEqual(
178+ http.client.NOT_FOUND, response.status_code, response.content)
179+
180 def test_delete_deletes_iprange(self):
181 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
182- iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
183+ iprange = factory.make_IPRange(
184+ subnet, '10.0.0.2', '10.0.0.10', user=self.logged_in_user)
185 uri = get_iprange_uri(iprange)
186 response = self.client.delete(uri)
187 self.assertEqual(
188 http.client.NO_CONTENT, response.status_code, response.content)
189 self.assertIsNone(reload_object(iprange))
190
191+ def test_delete_403_when_not_user(self):
192+ subnet = factory.make_Subnet(cidr="10.0.0.0/24")
193+ iprange = factory.make_IPRange(
194+ subnet, '10.0.0.2', '10.0.0.10', user=factory.make_User())
195+ uri = get_iprange_uri(iprange)
196+ response = self.client.delete(uri)
197+ self.assertEqual(
198+ http.client.FORBIDDEN, response.status_code, response.content)
199+ self.assertIsNotNone(reload_object(iprange))
200+
201 def test_delete_404_when_invalid_id(self):
202 uri = reverse(
203 'iprange_handler', args=[random.randint(100, 1000)])