]>
Commit | Line | Data |
---|---|---|
541bb91b | 1 | from __future__ import print_function |
e2dba705 | 2 | import json |
d29d5db7 | 3 | import time |
e2dba705 | 4 | import unittest |
ccfabd0d | 5 | from copy import deepcopy |
646bcd7d | 6 | from parameterized import parameterized |
6754ef71 | 7 | from pprint import pprint |
7cbc5255 | 8 | from test_helper import ApiTestCase, unique_zone_name, is_auth, is_recursor, get_db_records, pdnsutil_rectify |
6754ef71 CH |
9 | |
10 | ||
11 | def get_rrset(data, qname, qtype): | |
12 | for rrset in data['rrsets']: | |
13 | if rrset['name'] == qname and rrset['type'] == qtype: | |
14 | return rrset | |
15 | return None | |
16 | ||
17 | ||
18 | def get_first_rec(data, qname, qtype): | |
19 | rrset = get_rrset(data, qname, qtype) | |
20 | if rrset: | |
21 | return rrset['records'][0] | |
22 | return None | |
23 | ||
24 | ||
25 | def eq_zone_rrsets(rrsets, expected): | |
26 | data_got = {} | |
27 | data_expected = {} | |
541bb91b | 28 | for type_, expected_records in expected.items(): |
6754ef71 CH |
29 | type_ = str(type_) |
30 | data_got[type_] = set() | |
31 | data_expected[type_] = set() | |
32 | uses_name = any(['name' in expected_record for expected_record in expected_records]) | |
33 | # minify + convert received data | |
34 | for rrset in [rrset for rrset in rrsets if rrset['type'] == type_]: | |
541bb91b | 35 | print(rrset) |
6754ef71 CH |
36 | for r in rrset['records']: |
37 | data_got[type_].add((rrset['name'] if uses_name else '@', rrset['type'], r['content'])) | |
38 | # minify expected data | |
39 | for r in expected_records: | |
40 | data_expected[type_].add((r['name'] if uses_name else '@', type_, r['content'])) | |
41 | ||
541bb91b | 42 | print("eq_zone_rrsets: got:") |
6754ef71 | 43 | pprint(data_got) |
541bb91b | 44 | print("eq_zone_rrsets: expected:") |
6754ef71 CH |
45 | pprint(data_expected) |
46 | ||
47 | assert data_got == data_expected, "%r != %r" % (data_got, data_expected) | |
1a152698 CH |
48 | |
49 | ||
02945d9a | 50 | class Zones(ApiTestCase): |
1a152698 | 51 | |
80e9a517 PD |
52 | def _test_list_zones(self, dnssec=True): |
53 | path = "/api/v1/servers/localhost/zones" | |
54 | if not dnssec: | |
55 | path = path + "?dnssec=false" | |
56 | r = self.session.get(self.url(path)) | |
c1374bdb | 57 | self.assert_success_json(r) |
45de6290 | 58 | domains = r.json() |
02945d9a | 59 | example_com = [domain for domain in domains if domain['name'] in ('example.com', 'example.com.')] |
1a152698 CH |
60 | self.assertEquals(len(example_com), 1) |
61 | example_com = example_com[0] | |
a21e8566 | 62 | print(example_com) |
02945d9a | 63 | required_fields = ['id', 'url', 'name', 'kind'] |
c1374bdb | 64 | if is_auth(): |
80e9a517 PD |
65 | required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account'] |
66 | if dnssec: | |
67 | required_fields = required_fields = ['dnssec', 'edited_serial'] | |
a21e8566 | 68 | self.assertNotEquals(example_com['serial'], 0) |
a25bc5e9 PD |
69 | if not dnssec: |
70 | self.assertNotIn('dnssec', example_com) | |
c1374bdb | 71 | elif is_recursor(): |
02945d9a CH |
72 | required_fields = required_fields + ['recursion_desired', 'servers'] |
73 | for field in required_fields: | |
74 | self.assertIn(field, example_com) | |
75 | ||
80e9a517 | 76 | def test_list_zones_with_dnssec(self): |
a25bc5e9 PD |
77 | if is_auth(): |
78 | self._test_list_zones(True) | |
80e9a517 PD |
79 | |
80 | def test_list_zones_without_dnssec(self): | |
81 | self._test_list_zones(False) | |
02945d9a | 82 | |
406497f5 | 83 | class AuthZonesHelperMixin(object): |
284fdfe9 | 84 | def create_zone(self, name=None, **kwargs): |
bee2acae CH |
85 | if name is None: |
86 | name = unique_zone_name() | |
e2dba705 | 87 | payload = { |
bee2acae | 88 | 'name': name, |
e2dba705 | 89 | 'kind': 'Native', |
1d6b70f9 | 90 | 'nameservers': ['ns1.example.com.', 'ns2.example.com.'] |
e2dba705 | 91 | } |
284fdfe9 | 92 | for k, v in kwargs.items(): |
4bdff352 CH |
93 | if v is None: |
94 | del payload[k] | |
95 | else: | |
96 | payload[k] = v | |
541bb91b | 97 | print("sending", payload) |
e2dba705 | 98 | r = self.session.post( |
46d06a12 | 99 | self.url("/api/v1/servers/localhost/zones"), |
e2dba705 CH |
100 | data=json.dumps(payload), |
101 | headers={'content-type': 'application/json'}) | |
c1374bdb | 102 | self.assert_success_json(r) |
64a36f0d | 103 | self.assertEquals(r.status_code, 201) |
1d6b70f9 | 104 | reply = r.json() |
541bb91b | 105 | print("reply", reply) |
6754ef71 | 106 | return name, payload, reply |
bee2acae | 107 | |
406497f5 CH |
108 | |
109 | @unittest.skipIf(not is_auth(), "Not applicable") | |
110 | class AuthZones(ApiTestCase, AuthZonesHelperMixin): | |
111 | ||
c1374bdb | 112 | def test_create_zone(self): |
b0af9105 | 113 | # soa_edit_api has a default, override with empty for this test |
6754ef71 | 114 | name, payload, data = self.create_zone(serial=22, soa_edit_api='') |
1258fecd | 115 | for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'edited_serial', 'soa_edit_api', 'soa_edit', 'account'): |
d29d5db7 CH |
116 | self.assertIn(k, data) |
117 | if k in payload: | |
118 | self.assertEquals(data[k], payload[k]) | |
f63168e6 | 119 | # validate generated SOA |
6754ef71 | 120 | expected_soa = "a.misconfigured.powerdns.server. hostmaster." + name + " " + \ |
1d6b70f9 | 121 | str(payload['serial']) + " 10800 3600 604800 3600" |
f63168e6 | 122 | self.assertEquals( |
6754ef71 | 123 | get_first_rec(data, name, 'SOA')['content'], |
1d6b70f9 | 124 | expected_soa |
f63168e6 | 125 | ) |
1d6b70f9 | 126 | # Because we had confusion about dots, check that the DB is without dots. |
6754ef71 | 127 | dbrecs = get_db_records(name, 'SOA') |
1d6b70f9 | 128 | self.assertEqual(dbrecs[0]['content'], expected_soa.replace('. ', ' ')) |
1258fecd | 129 | self.assertNotEquals(data['serial'], data['edited_serial']) |
d29d5db7 | 130 | |
c1374bdb | 131 | def test_create_zone_with_soa_edit_api(self): |
f63168e6 | 132 | # soa_edit_api wins over serial |
6754ef71 | 133 | name, payload, data = self.create_zone(soa_edit_api='EPOCH', serial=10) |
f63168e6 | 134 | for k in ('soa_edit_api', ): |
e2dba705 CH |
135 | self.assertIn(k, data) |
136 | if k in payload: | |
137 | self.assertEquals(data[k], payload[k]) | |
f63168e6 | 138 | # generated EPOCH serial surely is > fixed serial we passed in |
541bb91b | 139 | print(data) |
f63168e6 | 140 | self.assertGreater(data['serial'], payload['serial']) |
6754ef71 | 141 | soa_serial = int(get_first_rec(data, name, 'SOA')['content'].split(' ')[2]) |
f63168e6 CH |
142 | self.assertGreater(soa_serial, payload['serial']) |
143 | self.assertEquals(soa_serial, data['serial']) | |
6bb25159 | 144 | |
79532aa7 CH |
145 | def test_create_zone_with_account(self): |
146 | # soa_edit_api wins over serial | |
6754ef71 | 147 | name, payload, data = self.create_zone(account='anaccount', serial=10) |
541bb91b | 148 | print(data) |
79532aa7 CH |
149 | for k in ('account', ): |
150 | self.assertIn(k, data) | |
151 | if k in payload: | |
152 | self.assertEquals(data[k], payload[k]) | |
153 | ||
9440a9f0 CH |
154 | def test_create_zone_default_soa_edit_api(self): |
155 | name, payload, data = self.create_zone() | |
541bb91b | 156 | print(data) |
9440a9f0 CH |
157 | self.assertEquals(data['soa_edit_api'], 'DEFAULT') |
158 | ||
331d3062 CH |
159 | def test_create_zone_exists(self): |
160 | name, payload, data = self.create_zone() | |
161 | print(data) | |
162 | payload = { | |
163 | 'name': name, | |
164 | 'kind': 'Native' | |
165 | } | |
166 | print(payload) | |
167 | r = self.session.post( | |
168 | self.url("/api/v1/servers/localhost/zones"), | |
169 | data=json.dumps(payload), | |
170 | headers={'content-type': 'application/json'}) | |
171 | self.assertEquals(r.status_code, 409) # Conflict - already exists | |
172 | ||
01f7df3f CH |
173 | def test_create_zone_with_soa_edit(self): |
174 | name, payload, data = self.create_zone(soa_edit='INCEPTION-INCREMENT', soa_edit_api='SOA-EDIT-INCREASE') | |
541bb91b | 175 | print(data) |
01f7df3f CH |
176 | self.assertEquals(data['soa_edit'], 'INCEPTION-INCREMENT') |
177 | self.assertEquals(data['soa_edit_api'], 'SOA-EDIT-INCREASE') | |
178 | soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2] | |
179 | # These particular settings lead to the first serial set to YYYYMMDD01. | |
180 | self.assertEquals(soa_serial[-2:], '01') | |
f613d242 CH |
181 | rrset = { |
182 | 'changetype': 'replace', | |
183 | 'name': name, | |
184 | 'type': 'A', | |
185 | 'ttl': 3600, | |
186 | 'records': [ | |
187 | { | |
188 | "content": "127.0.0.1", | |
189 | "disabled": False | |
190 | } | |
191 | ] | |
192 | } | |
193 | payload = {'rrsets': [rrset]} | |
194 | self.session.patch( | |
195 | self.url("/api/v1/servers/localhost/zones/" + data['id']), | |
196 | data=json.dumps(payload), | |
197 | headers={'content-type': 'application/json'}) | |
198 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])) | |
199 | data = r.json() | |
200 | soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2] | |
201 | self.assertEquals(soa_serial[-2:], '02') | |
01f7df3f | 202 | |
c1374bdb | 203 | def test_create_zone_with_records(self): |
f63168e6 | 204 | name = unique_zone_name() |
6754ef71 CH |
205 | rrset = { |
206 | "name": name, | |
207 | "type": "A", | |
208 | "ttl": 3600, | |
209 | "records": [{ | |
f63168e6 | 210 | "content": "4.3.2.1", |
6754ef71 CH |
211 | "disabled": False, |
212 | }], | |
213 | } | |
214 | name, payload, data = self.create_zone(name=name, rrsets=[rrset]) | |
f63168e6 | 215 | # check our record has appeared |
6754ef71 | 216 | self.assertEquals(get_rrset(data, name, 'A')['records'], rrset['records']) |
f63168e6 | 217 | |
d0953126 AT |
218 | def test_create_zone_with_wildcard_records(self): |
219 | name = unique_zone_name() | |
6754ef71 CH |
220 | rrset = { |
221 | "name": "*."+name, | |
222 | "type": "A", | |
223 | "ttl": 3600, | |
224 | "records": [{ | |
d0953126 | 225 | "content": "4.3.2.1", |
6754ef71 CH |
226 | "disabled": False, |
227 | }], | |
228 | } | |
229 | name, payload, data = self.create_zone(name=name, rrsets=[rrset]) | |
d0953126 | 230 | # check our record has appeared |
6754ef71 | 231 | self.assertEquals(get_rrset(data, rrset['name'], 'A')['records'], rrset['records']) |
d0953126 | 232 | |
c1374bdb | 233 | def test_create_zone_with_comments(self): |
f63168e6 | 234 | name = unique_zone_name() |
f2d6dcc0 RG |
235 | rrsets = [ |
236 | { | |
237 | "name": name, | |
238 | "type": "soa", # test uppercasing of type, too. | |
239 | "comments": [{ | |
240 | "account": "test1", | |
241 | "content": "blah blah", | |
242 | "modified_at": 11112, | |
243 | }], | |
244 | }, | |
245 | { | |
246 | "name": name, | |
247 | "type": "AAAA", | |
248 | "ttl": 3600, | |
249 | "records": [{ | |
250 | "content": "2001:DB8::1", | |
251 | "disabled": False, | |
252 | }], | |
253 | "comments": [{ | |
254 | "account": "test AAAA", | |
255 | "content": "blah blah AAAA", | |
256 | "modified_at": 11112, | |
257 | }], | |
258 | }, | |
259 | { | |
260 | "name": name, | |
261 | "type": "TXT", | |
262 | "ttl": 3600, | |
263 | "records": [{ | |
264 | "content": "\"test TXT\"", | |
265 | "disabled": False, | |
266 | }], | |
267 | }, | |
268 | { | |
269 | "name": name, | |
270 | "type": "A", | |
271 | "ttl": 3600, | |
272 | "records": [{ | |
273 | "content": "192.0.2.1", | |
274 | "disabled": False, | |
275 | }], | |
276 | }, | |
277 | ] | |
278 | name, payload, data = self.create_zone(name=name, rrsets=rrsets) | |
279 | # NS records have been created | |
280 | self.assertEquals(len(data['rrsets']), len(rrsets) + 1) | |
f63168e6 | 281 | # check our comment has appeared |
f2d6dcc0 RG |
282 | self.assertEquals(get_rrset(data, name, 'SOA')['comments'], rrsets[0]['comments']) |
283 | self.assertEquals(get_rrset(data, name, 'A')['comments'], []) | |
284 | self.assertEquals(get_rrset(data, name, 'TXT')['comments'], []) | |
285 | self.assertEquals(get_rrset(data, name, 'AAAA')['comments'], rrsets[1]['comments']) | |
f63168e6 | 286 | |
1d6b70f9 CH |
287 | def test_create_zone_uncanonical_nameservers(self): |
288 | name = unique_zone_name() | |
289 | payload = { | |
290 | 'name': name, | |
291 | 'kind': 'Native', | |
292 | 'nameservers': ['uncanon.example.com'] | |
293 | } | |
541bb91b | 294 | print(payload) |
1d6b70f9 CH |
295 | r = self.session.post( |
296 | self.url("/api/v1/servers/localhost/zones"), | |
297 | data=json.dumps(payload), | |
298 | headers={'content-type': 'application/json'}) | |
299 | self.assertEquals(r.status_code, 422) | |
300 | self.assertIn('Nameserver is not canonical', r.json()['error']) | |
301 | ||
302 | def test_create_auth_zone_no_name(self): | |
303 | name = unique_zone_name() | |
304 | payload = { | |
305 | 'name': '', | |
306 | 'kind': 'Native', | |
307 | } | |
541bb91b | 308 | print(payload) |
1d6b70f9 CH |
309 | r = self.session.post( |
310 | self.url("/api/v1/servers/localhost/zones"), | |
311 | data=json.dumps(payload), | |
312 | headers={'content-type': 'application/json'}) | |
313 | self.assertEquals(r.status_code, 422) | |
314 | self.assertIn('is not canonical', r.json()['error']) | |
315 | ||
c1374bdb | 316 | def test_create_zone_with_custom_soa(self): |
f63168e6 | 317 | name = unique_zone_name() |
6754ef71 CH |
318 | content = u"ns1.example.net. testmaster@example.net. 10 10800 3600 604800 3600" |
319 | rrset = { | |
320 | "name": name, | |
321 | "type": "soa", # test uppercasing of type, too. | |
322 | "ttl": 3600, | |
323 | "records": [{ | |
324 | "content": content, | |
325 | "disabled": False, | |
326 | }], | |
327 | } | |
328 | name, payload, data = self.create_zone(name=name, rrsets=[rrset], soa_edit_api='') | |
329 | self.assertEquals(get_rrset(data, name, 'SOA')['records'], rrset['records']) | |
330 | dbrecs = get_db_records(name, 'SOA') | |
331 | self.assertEqual(dbrecs[0]['content'], content.replace('. ', ' ')) | |
1d6b70f9 CH |
332 | |
333 | def test_create_zone_double_dot(self): | |
334 | name = 'test..' + unique_zone_name() | |
335 | payload = { | |
336 | 'name': name, | |
337 | 'kind': 'Native', | |
338 | 'nameservers': ['ns1.example.com.'] | |
339 | } | |
541bb91b | 340 | print(payload) |
1d6b70f9 CH |
341 | r = self.session.post( |
342 | self.url("/api/v1/servers/localhost/zones"), | |
343 | data=json.dumps(payload), | |
344 | headers={'content-type': 'application/json'}) | |
345 | self.assertEquals(r.status_code, 422) | |
346 | self.assertIn('Unable to parse DNS Name', r.json()['error']) | |
05776d2f | 347 | |
1d6b70f9 CH |
348 | def test_create_zone_restricted_chars(self): |
349 | name = 'test:' + unique_zone_name() # : isn't good as a name. | |
350 | payload = { | |
351 | 'name': name, | |
352 | 'kind': 'Native', | |
353 | 'nameservers': ['ns1.example.com'] | |
354 | } | |
541bb91b | 355 | print(payload) |
1d6b70f9 CH |
356 | r = self.session.post( |
357 | self.url("/api/v1/servers/localhost/zones"), | |
358 | data=json.dumps(payload), | |
359 | headers={'content-type': 'application/json'}) | |
360 | self.assertEquals(r.status_code, 422) | |
361 | self.assertIn('contains unsupported characters', r.json()['error']) | |
4ebf78b1 | 362 | |
33e6c3e9 CH |
363 | def test_create_zone_mixed_nameservers_ns_rrset_zonelevel(self): |
364 | name = unique_zone_name() | |
365 | rrset = { | |
366 | "name": name, | |
367 | "type": "NS", | |
368 | "ttl": 3600, | |
369 | "records": [{ | |
370 | "content": "ns2.example.com.", | |
371 | "disabled": False, | |
372 | }], | |
373 | } | |
374 | payload = { | |
375 | 'name': name, | |
376 | 'kind': 'Native', | |
377 | 'nameservers': ['ns1.example.com.'], | |
378 | 'rrsets': [rrset], | |
379 | } | |
541bb91b | 380 | print(payload) |
33e6c3e9 CH |
381 | r = self.session.post( |
382 | self.url("/api/v1/servers/localhost/zones"), | |
383 | data=json.dumps(payload), | |
384 | headers={'content-type': 'application/json'}) | |
385 | self.assertEquals(r.status_code, 422) | |
386 | self.assertIn('Nameservers list MUST NOT be mixed with zone-level NS in rrsets', r.json()['error']) | |
387 | ||
388 | def test_create_zone_mixed_nameservers_ns_rrset_below_zonelevel(self): | |
389 | name = unique_zone_name() | |
390 | rrset = { | |
391 | "name": 'subzone.'+name, | |
392 | "type": "NS", | |
393 | "ttl": 3600, | |
394 | "records": [{ | |
395 | "content": "ns2.example.com.", | |
396 | "disabled": False, | |
397 | }], | |
398 | } | |
399 | payload = { | |
400 | 'name': name, | |
401 | 'kind': 'Native', | |
402 | 'nameservers': ['ns1.example.com.'], | |
403 | 'rrsets': [rrset], | |
404 | } | |
541bb91b | 405 | print(payload) |
33e6c3e9 CH |
406 | r = self.session.post( |
407 | self.url("/api/v1/servers/localhost/zones"), | |
408 | data=json.dumps(payload), | |
409 | headers={'content-type': 'application/json'}) | |
410 | self.assert_success_json(r) | |
411 | ||
c1374bdb | 412 | def test_create_zone_with_symbols(self): |
6754ef71 | 413 | name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name()) |
bee2acae | 414 | name = payload['name'] |
1d6b70f9 | 415 | expected_id = name.replace('/', '=2F') |
00a9b229 CH |
416 | for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'): |
417 | self.assertIn(k, data) | |
418 | if k in payload: | |
419 | self.assertEquals(data[k], payload[k]) | |
bee2acae | 420 | self.assertEquals(data['id'], expected_id) |
1d6b70f9 CH |
421 | dbrecs = get_db_records(name, 'SOA') |
422 | self.assertEqual(dbrecs[0]['name'], name.rstrip('.')) | |
00a9b229 | 423 | |
c1374bdb | 424 | def test_create_zone_with_nameservers_non_string(self): |
e90b4e38 CH |
425 | # ensure we don't crash |
426 | name = unique_zone_name() | |
427 | payload = { | |
428 | 'name': name, | |
429 | 'kind': 'Native', | |
430 | 'nameservers': [{'a': 'ns1.example.com'}] # invalid | |
431 | } | |
541bb91b | 432 | print(payload) |
e90b4e38 | 433 | r = self.session.post( |
46d06a12 | 434 | self.url("/api/v1/servers/localhost/zones"), |
e90b4e38 CH |
435 | data=json.dumps(payload), |
436 | headers={'content-type': 'application/json'}) | |
437 | self.assertEquals(r.status_code, 422) | |
438 | ||
986e4858 PL |
439 | def test_create_zone_with_dnssec(self): |
440 | """ | |
441 | Create a zone with "dnssec" set and see if a key was made. | |
442 | """ | |
443 | name = unique_zone_name() | |
444 | name, payload, data = self.create_zone(dnssec=True) | |
445 | ||
446 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
447 | ||
448 | for k in ('dnssec', ): | |
449 | self.assertIn(k, data) | |
450 | if k in payload: | |
451 | self.assertEquals(data[k], payload[k]) | |
452 | ||
453 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/cryptokeys')) | |
454 | ||
455 | keys = r.json() | |
456 | ||
541bb91b | 457 | print(keys) |
986e4858 PL |
458 | |
459 | self.assertEquals(r.status_code, 200) | |
460 | self.assertEquals(len(keys), 1) | |
461 | self.assertEquals(keys[0]['type'], 'Cryptokey') | |
462 | self.assertEquals(keys[0]['active'], True) | |
463 | self.assertEquals(keys[0]['keytype'], 'csk') | |
464 | ||
cbe8b186 PL |
465 | def test_create_zone_with_dnssec_disable_dnssec(self): |
466 | """ | |
467 | Create a zone with "dnssec", then set "dnssec" to false and see if the | |
468 | keys are gone | |
469 | """ | |
470 | name = unique_zone_name() | |
471 | name, payload, data = self.create_zone(dnssec=True) | |
472 | ||
473 | self.session.put(self.url("/api/v1/servers/localhost/zones/" + name), | |
474 | data=json.dumps({'dnssec': False})) | |
475 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
476 | ||
477 | zoneinfo = r.json() | |
478 | ||
479 | self.assertEquals(r.status_code, 200) | |
480 | self.assertEquals(zoneinfo['dnssec'], False) | |
481 | ||
482 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/cryptokeys')) | |
483 | ||
484 | keys = r.json() | |
485 | ||
486 | self.assertEquals(r.status_code, 200) | |
487 | self.assertEquals(len(keys), 0) | |
488 | ||
986e4858 PL |
489 | def test_create_zone_with_nsec3param(self): |
490 | """ | |
491 | Create a zone with "nsec3param" set and see if the metadata was added. | |
492 | """ | |
493 | name = unique_zone_name() | |
494 | nsec3param = '1 0 500 aabbccddeeff' | |
495 | name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param) | |
496 | ||
497 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
498 | ||
499 | for k in ('dnssec', 'nsec3param'): | |
500 | self.assertIn(k, data) | |
501 | if k in payload: | |
502 | self.assertEquals(data[k], payload[k]) | |
503 | ||
504 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/metadata/NSEC3PARAM')) | |
505 | ||
506 | data = r.json() | |
507 | ||
541bb91b | 508 | print(data) |
986e4858 PL |
509 | |
510 | self.assertEquals(r.status_code, 200) | |
511 | self.assertEquals(len(data['metadata']), 1) | |
512 | self.assertEquals(data['kind'], 'NSEC3PARAM') | |
513 | self.assertEquals(data['metadata'][0], nsec3param) | |
514 | ||
515 | def test_create_zone_with_nsec3narrow(self): | |
516 | """ | |
517 | Create a zone with "nsec3narrow" set and see if the metadata was added. | |
518 | """ | |
519 | name = unique_zone_name() | |
520 | nsec3param = '1 0 500 aabbccddeeff' | |
521 | name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param, | |
522 | nsec3narrow=True) | |
523 | ||
524 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
525 | ||
526 | for k in ('dnssec', 'nsec3param', 'nsec3narrow'): | |
527 | self.assertIn(k, data) | |
528 | if k in payload: | |
529 | self.assertEquals(data[k], payload[k]) | |
530 | ||
531 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/metadata/NSEC3NARROW')) | |
532 | ||
533 | data = r.json() | |
534 | ||
541bb91b | 535 | print(data) |
986e4858 PL |
536 | |
537 | self.assertEquals(r.status_code, 200) | |
538 | self.assertEquals(len(data['metadata']), 1) | |
539 | self.assertEquals(data['kind'], 'NSEC3NARROW') | |
540 | self.assertEquals(data['metadata'][0], '1') | |
541 | ||
a843c67e KM |
542 | def test_create_zone_dnssec_serial(self): |
543 | """ | |
544 | Create a zone set/unset "dnssec" and see if the serial was increased | |
545 | after every step | |
546 | """ | |
547 | name = unique_zone_name() | |
548 | name, payload, data = self.create_zone() | |
549 | ||
550 | soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2] | |
551 | self.assertEquals(soa_serial[-2:], '01') | |
552 | ||
553 | self.session.put(self.url("/api/v1/servers/localhost/zones/" + name), | |
554 | data=json.dumps({'dnssec': True})) | |
555 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
556 | ||
557 | data = r.json() | |
558 | soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2] | |
559 | ||
560 | self.assertEquals(r.status_code, 200) | |
561 | self.assertEquals(soa_serial[-2:], '02') | |
562 | ||
563 | self.session.put(self.url("/api/v1/servers/localhost/zones/" + name), | |
564 | data=json.dumps({'dnssec': False})) | |
565 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)) | |
566 | ||
567 | data = r.json() | |
568 | soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2] | |
569 | ||
570 | self.assertEquals(r.status_code, 200) | |
571 | self.assertEquals(soa_serial[-2:], '03') | |
572 | ||
16e25450 CH |
573 | def test_zone_absolute_url(self): |
574 | name, payload, data = self.create_zone() | |
575 | r = self.session.get(self.url("/api/v1/servers/localhost/zones")) | |
576 | rdata = r.json() | |
577 | print(rdata[0]) | |
578 | self.assertTrue(rdata[0]['url'].startswith('/api/v')) | |
579 | ||
24e11043 CJ |
580 | def test_create_zone_metadata(self): |
581 | payload_metadata = {"type": "Metadata", "kind": "AXFR-SOURCE", "metadata": ["127.0.0.2"]} | |
582 | r = self.session.post(self.url("/api/v1/servers/localhost/zones/example.com/metadata"), | |
583 | data=json.dumps(payload_metadata)) | |
584 | rdata = r.json() | |
585 | self.assertEquals(r.status_code, 201) | |
586 | self.assertEquals(rdata["metadata"], payload_metadata["metadata"]) | |
587 | ||
588 | def test_create_zone_metadata_kind(self): | |
589 | payload_metadata = {"metadata": ["127.0.0.2"]} | |
590 | r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"), | |
591 | data=json.dumps(payload_metadata)) | |
592 | rdata = r.json() | |
593 | self.assertEquals(r.status_code, 200) | |
594 | self.assertEquals(rdata["metadata"], payload_metadata["metadata"]) | |
595 | ||
596 | def test_create_protected_zone_metadata(self): | |
597 | # test whether it prevents modification of certain kinds | |
598 | for k in ("NSEC3NARROW", "NSEC3PARAM", "PRESIGNED", "LUA-AXFR-SCRIPT"): | |
599 | payload = {"metadata": ["FOO", "BAR"]} | |
600 | r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/%s" % k), | |
601 | data=json.dumps(payload)) | |
602 | self.assertEquals(r.status_code, 422) | |
603 | ||
604 | def test_retrieve_zone_metadata(self): | |
605 | payload_metadata = {"type": "Metadata", "kind": "AXFR-SOURCE", "metadata": ["127.0.0.2"]} | |
606 | self.session.post(self.url("/api/v1/servers/localhost/zones/example.com/metadata"), | |
607 | data=json.dumps(payload_metadata)) | |
608 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata")) | |
609 | rdata = r.json() | |
610 | self.assertEquals(r.status_code, 200) | |
611 | self.assertIn(payload_metadata, rdata) | |
612 | ||
613 | def test_delete_zone_metadata(self): | |
614 | r = self.session.delete(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE")) | |
615 | self.assertEquals(r.status_code, 200) | |
616 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE")) | |
617 | rdata = r.json() | |
618 | self.assertEquals(r.status_code, 200) | |
619 | self.assertEquals(rdata["metadata"], []) | |
620 | ||
9ac4e6d5 PL |
621 | def test_create_external_zone_metadata(self): |
622 | payload_metadata = {"metadata": ["My very important message"]} | |
623 | r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/X-MYMETA"), | |
624 | data=json.dumps(payload_metadata)) | |
625 | self.assertEquals(r.status_code, 200) | |
626 | rdata = r.json() | |
627 | self.assertEquals(rdata["metadata"], payload_metadata["metadata"]) | |
628 | ||
d38e81e6 PL |
629 | def test_create_metadata_in_non_existent_zone(self): |
630 | payload_metadata = {"type": "Metadata", "kind": "AXFR-SOURCE", "metadata": ["127.0.0.2"]} | |
631 | r = self.session.post(self.url("/api/v1/servers/localhost/zones/idonotexist.123.456.example./metadata"), | |
632 | data=json.dumps(payload_metadata)) | |
77bfe8de PL |
633 | self.assertEquals(r.status_code, 404) |
634 | # Note: errors should probably contain json (see #5988) | |
635 | # self.assertIn('Could not find domain ', r.json()['error']) | |
d38e81e6 | 636 | |
4bdff352 CH |
637 | def test_create_slave_zone(self): |
638 | # Test that nameservers can be absent for slave zones. | |
6754ef71 | 639 | name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2']) |
4bdff352 CH |
640 | for k in ('name', 'masters', 'kind'): |
641 | self.assertIn(k, data) | |
642 | self.assertEquals(data[k], payload[k]) | |
541bb91b CH |
643 | print("payload:", payload) |
644 | print("data:", data) | |
4de11a54 | 645 | # Because slave zones don't get a SOA, we need to test that they'll show up in the zone list. |
46d06a12 | 646 | r = self.session.get(self.url("/api/v1/servers/localhost/zones")) |
4de11a54 | 647 | zonelist = r.json() |
541bb91b | 648 | print("zonelist:", zonelist) |
4de11a54 CH |
649 | self.assertIn(payload['name'], [zone['name'] for zone in zonelist]) |
650 | # Also test that fetching the zone works. | |
46d06a12 | 651 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])) |
4de11a54 | 652 | data = r.json() |
541bb91b | 653 | print("zone (fetched):", data) |
4de11a54 CH |
654 | for k in ('name', 'masters', 'kind'): |
655 | self.assertIn(k, data) | |
656 | self.assertEquals(data[k], payload[k]) | |
657 | self.assertEqual(data['serial'], 0) | |
6754ef71 | 658 | self.assertEqual(data['rrsets'], []) |
4de11a54 | 659 | |
e543cc8f CH |
660 | def test_find_zone_by_name(self): |
661 | name = 'foo/' + unique_zone_name() | |
662 | name, payload, data = self.create_zone(name=name) | |
663 | r = self.session.get(self.url("/api/v1/servers/localhost/zones?zone=" + name)) | |
664 | data = r.json() | |
665 | print(data) | |
666 | self.assertEquals(data[0]['name'], name) | |
667 | ||
4de11a54 | 668 | def test_delete_slave_zone(self): |
6754ef71 | 669 | name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2']) |
46d06a12 | 670 | r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id'])) |
4de11a54 | 671 | r.raise_for_status() |
4bdff352 | 672 | |
a426cb89 | 673 | def test_retrieve_slave_zone(self): |
6754ef71 | 674 | name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2']) |
541bb91b CH |
675 | print("payload:", payload) |
676 | print("data:", data) | |
46d06a12 | 677 | r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/axfr-retrieve")) |
a426cb89 | 678 | data = r.json() |
541bb91b | 679 | print("status for axfr-retrieve:", data) |
a426cb89 CH |
680 | self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] + |
681 | '\' from master 127.0.0.2') | |
682 | ||
683 | def test_notify_master_zone(self): | |
6754ef71 | 684 | name, payload, data = self.create_zone(kind='Master') |
541bb91b CH |
685 | print("payload:", payload) |
686 | print("data:", data) | |
46d06a12 | 687 | r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/notify")) |
a426cb89 | 688 | data = r.json() |
541bb91b | 689 | print("status for notify:", data) |
a426cb89 CH |
690 | self.assertEqual(data['result'], 'Notification queued') |
691 | ||
c1374bdb | 692 | def test_get_zone_with_symbols(self): |
6754ef71 | 693 | name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name()) |
3c3c006b | 694 | name = payload['name'] |
1d6b70f9 | 695 | zone_id = (name.replace('/', '=2F')) |
46d06a12 | 696 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)) |
c1374bdb | 697 | data = r.json() |
6bb25159 | 698 | for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'): |
3c3c006b CH |
699 | self.assertIn(k, data) |
700 | if k in payload: | |
701 | self.assertEquals(data[k], payload[k]) | |
702 | ||
c1374bdb | 703 | def test_get_zone(self): |
46d06a12 | 704 | r = self.session.get(self.url("/api/v1/servers/localhost/zones")) |
05776d2f | 705 | domains = r.json() |
1d6b70f9 | 706 | example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0] |
46d06a12 | 707 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id'])) |
c1374bdb | 708 | self.assert_success_json(r) |
05776d2f CH |
709 | data = r.json() |
710 | for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'): | |
711 | self.assertIn(k, data) | |
1d6b70f9 | 712 | self.assertEquals(data['name'], 'example.com.') |
7c0ba3d2 | 713 | |
0f0e73fe | 714 | def test_import_zone_broken(self): |
646bcd7d CH |
715 | payload = { |
716 | 'name': 'powerdns-broken.com', | |
717 | 'kind': 'Master', | |
718 | 'nameservers': [], | |
719 | } | |
0f0e73fe MS |
720 | payload['zone'] = """ |
721 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571 | |
722 | flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 | |
723 | ;; WARNING: recursion requested but not available | |
724 | ||
725 | ;; OPT PSEUDOSECTION: | |
726 | ; EDNS: version: 0, flags:; udp: 1680 | |
727 | ;; QUESTION SECTION: | |
728 | ;powerdns.com. IN SOA | |
729 | ||
730 | ;; ANSWER SECTION: | |
731 | powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
732 | powerdns-broken.com. 3600 IN NS powerdnssec2.ds9a.nl. | |
733 | powerdns-broken.com. 3600 IN AAAA 2001:888:2000:1d::2 | |
734 | powerdns-broken.com. 86400 IN A 82.94.213.34 | |
735 | powerdns-broken.com. 3600 IN MX 0 xs.powerdns.com. | |
736 | powerdns-broken.com. 3600 IN NS powerdnssec1.ds9a.nl. | |
737 | powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
738 | """ | |
0f0e73fe | 739 | r = self.session.post( |
46d06a12 | 740 | self.url("/api/v1/servers/localhost/zones"), |
0f0e73fe MS |
741 | data=json.dumps(payload), |
742 | headers={'content-type': 'application/json'}) | |
743 | self.assertEquals(r.status_code, 422) | |
744 | ||
1d6b70f9 CH |
745 | def test_import_zone_axfr_outofzone(self): |
746 | # Ensure we don't create out-of-zone records | |
646bcd7d CH |
747 | payload = { |
748 | 'name': unique_zone_name(), | |
749 | 'kind': 'Master', | |
750 | 'nameservers': [], | |
751 | } | |
1d6b70f9 | 752 | payload['zone'] = """ |
646bcd7d CH |
753 | %NAME% 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 |
754 | %NAME% 3600 IN NS powerdnssec2.ds9a.nl. | |
1d6b70f9 | 755 | example.org. 3600 IN AAAA 2001:888:2000:1d::2 |
646bcd7d CH |
756 | %NAME% 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 |
757 | """.replace('%NAME%', payload['name']) | |
1d6b70f9 CH |
758 | r = self.session.post( |
759 | self.url("/api/v1/servers/localhost/zones"), | |
760 | data=json.dumps(payload), | |
761 | headers={'content-type': 'application/json'}) | |
762 | self.assertEquals(r.status_code, 422) | |
763 | self.assertEqual(r.json()['error'], 'RRset example.org. IN AAAA: Name is out of zone') | |
764 | ||
0f0e73fe | 765 | def test_import_zone_axfr(self): |
646bcd7d CH |
766 | payload = { |
767 | 'name': 'powerdns.com.', | |
768 | 'kind': 'Master', | |
769 | 'nameservers': [], | |
770 | 'soa_edit_api': '', # turn off so exact SOA comparison works. | |
771 | } | |
0f0e73fe MS |
772 | payload['zone'] = """ |
773 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571 | |
774 | ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 | |
775 | ;; WARNING: recursion requested but not available | |
776 | ||
777 | ;; OPT PSEUDOSECTION: | |
778 | ; EDNS: version: 0, flags:; udp: 1680 | |
779 | ;; QUESTION SECTION: | |
780 | ;powerdns.com. IN SOA | |
781 | ||
782 | ;; ANSWER SECTION: | |
783 | powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
784 | powerdns.com. 3600 IN NS powerdnssec2.ds9a.nl. | |
785 | powerdns.com. 3600 IN AAAA 2001:888:2000:1d::2 | |
786 | powerdns.com. 86400 IN A 82.94.213.34 | |
787 | powerdns.com. 3600 IN MX 0 xs.powerdns.com. | |
788 | powerdns.com. 3600 IN NS powerdnssec1.ds9a.nl. | |
789 | powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
790 | """ | |
0f0e73fe | 791 | r = self.session.post( |
46d06a12 | 792 | self.url("/api/v1/servers/localhost/zones"), |
0f0e73fe MS |
793 | data=json.dumps(payload), |
794 | headers={'content-type': 'application/json'}) | |
795 | self.assert_success_json(r) | |
796 | data = r.json() | |
797 | self.assertIn('name', data) | |
0f0e73fe | 798 | |
90568eb2 MS |
799 | expected = { |
800 | 'NS': [ | |
6754ef71 CH |
801 | {'content': 'powerdnssec1.ds9a.nl.'}, |
802 | {'content': 'powerdnssec2.ds9a.nl.'}, | |
803 | ], | |
90568eb2 | 804 | 'SOA': [ |
6754ef71 CH |
805 | {'content': 'powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800'}, |
806 | ], | |
90568eb2 | 807 | 'MX': [ |
6754ef71 CH |
808 | {'content': '0 xs.powerdns.com.'}, |
809 | ], | |
90568eb2 | 810 | 'A': [ |
6754ef71 CH |
811 | {'content': '82.94.213.34', 'name': 'powerdns.com.'}, |
812 | ], | |
90568eb2 | 813 | 'AAAA': [ |
6754ef71 CH |
814 | {'content': '2001:888:2000:1d::2', 'name': 'powerdns.com.'}, |
815 | ], | |
90568eb2 | 816 | } |
0f0e73fe | 817 | |
6754ef71 | 818 | eq_zone_rrsets(data['rrsets'], expected) |
1d6b70f9 | 819 | |
e3675a8a | 820 | # check content in DB is stored WITHOUT trailing dot. |
1d6b70f9 | 821 | dbrecs = get_db_records(payload['name'], 'NS') |
e3675a8a CH |
822 | dbrec = next((dbrec for dbrec in dbrecs if dbrec['content'].startswith('powerdnssec1'))) |
823 | self.assertEqual(dbrec['content'], 'powerdnssec1.ds9a.nl') | |
0f0e73fe MS |
824 | |
825 | def test_import_zone_bind(self): | |
646bcd7d CH |
826 | payload = { |
827 | 'name': 'example.org.', | |
828 | 'kind': 'Master', | |
829 | 'nameservers': [], | |
830 | 'soa_edit_api': '', # turn off so exact SOA comparison works. | |
831 | } | |
0f0e73fe MS |
832 | payload['zone'] = """ |
833 | $TTL 86400 ; 24 hours could have been written as 24h or 1d | |
834 | ; $TTL used for all RRs without explicit TTL value | |
835 | $ORIGIN example.org. | |
836 | @ 1D IN SOA ns1.example.org. hostmaster.example.org. ( | |
837 | 2002022401 ; serial | |
838 | 3H ; refresh | |
839 | 15 ; retry | |
840 | 1w ; expire | |
841 | 3h ; minimum | |
842 | ) | |
843 | IN NS ns1.example.org. ; in the domain | |
844 | IN NS ns2.smokeyjoe.com. ; external to domain | |
845 | IN MX 10 mail.another.com. ; external mail provider | |
846 | ; server host definitions | |
1d6b70f9 | 847 | ns1 IN A 192.168.0.1 ;name server definition |
0f0e73fe MS |
848 | www IN A 192.168.0.2 ;web server definition |
849 | ftp IN CNAME www.example.org. ;ftp server definition | |
850 | ; non server domain hosts | |
851 | bill IN A 192.168.0.3 | |
1d6b70f9 | 852 | fred IN A 192.168.0.4 |
0f0e73fe | 853 | """ |
0f0e73fe | 854 | r = self.session.post( |
46d06a12 | 855 | self.url("/api/v1/servers/localhost/zones"), |
0f0e73fe MS |
856 | data=json.dumps(payload), |
857 | headers={'content-type': 'application/json'}) | |
858 | self.assert_success_json(r) | |
859 | data = r.json() | |
860 | self.assertIn('name', data) | |
0f0e73fe | 861 | |
90568eb2 MS |
862 | expected = { |
863 | 'NS': [ | |
6754ef71 CH |
864 | {'content': 'ns1.example.org.'}, |
865 | {'content': 'ns2.smokeyjoe.com.'}, | |
866 | ], | |
90568eb2 | 867 | 'SOA': [ |
6754ef71 CH |
868 | {'content': 'ns1.example.org. hostmaster.example.org. 2002022401 10800 15 604800 10800'}, |
869 | ], | |
90568eb2 | 870 | 'MX': [ |
6754ef71 CH |
871 | {'content': '10 mail.another.com.'}, |
872 | ], | |
90568eb2 | 873 | 'A': [ |
6754ef71 CH |
874 | {'content': '192.168.0.1', 'name': 'ns1.example.org.'}, |
875 | {'content': '192.168.0.2', 'name': 'www.example.org.'}, | |
876 | {'content': '192.168.0.3', 'name': 'bill.example.org.'}, | |
877 | {'content': '192.168.0.4', 'name': 'fred.example.org.'}, | |
878 | ], | |
90568eb2 | 879 | 'CNAME': [ |
6754ef71 CH |
880 | {'content': 'www.example.org.', 'name': 'ftp.example.org.'}, |
881 | ], | |
90568eb2 | 882 | } |
0f0e73fe | 883 | |
6754ef71 | 884 | eq_zone_rrsets(data['rrsets'], expected) |
0f0e73fe | 885 | |
646bcd7d CH |
886 | def test_import_zone_bind_cname_apex(self): |
887 | payload = { | |
888 | 'name': unique_zone_name(), | |
889 | 'kind': 'Master', | |
890 | 'nameservers': [], | |
891 | } | |
892 | payload['zone'] = """ | |
893 | $ORIGIN %NAME% | |
894 | @ IN SOA ns1.example.org. hostmaster.example.org. (2002022401 3H 15 1W 3H) | |
895 | @ IN NS ns1.example.org. | |
896 | @ IN NS ns2.smokeyjoe.com. | |
897 | @ IN CNAME www.example.org. | |
898 | """.replace('%NAME%', payload['name']) | |
899 | r = self.session.post( | |
900 | self.url("/api/v1/servers/localhost/zones"), | |
901 | data=json.dumps(payload), | |
902 | headers={'content-type': 'application/json'}) | |
903 | self.assertEquals(r.status_code, 422) | |
904 | self.assertIn('Conflicts with another RRset', r.json()['error']) | |
905 | ||
c1374bdb | 906 | def test_export_zone_json(self): |
6754ef71 | 907 | name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='') |
a83004d3 CH |
908 | # export it |
909 | r = self.session.get( | |
46d06a12 | 910 | self.url("/api/v1/servers/localhost/zones/" + name + "/export"), |
a83004d3 CH |
911 | headers={'accept': 'application/json;q=0.9,*/*;q=0.8'} |
912 | ) | |
c1374bdb | 913 | self.assert_success_json(r) |
a83004d3 CH |
914 | data = r.json() |
915 | self.assertIn('zone', data) | |
ba2a1254 DK |
916 | expected_data = [name + '\t3600\tIN\tNS\tns1.foo.com.', |
917 | name + '\t3600\tIN\tNS\tns2.foo.com.', | |
918 | name + '\t3600\tIN\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name + | |
1d6b70f9 | 919 | ' 0 10800 3600 604800 3600'] |
a83004d3 CH |
920 | self.assertEquals(data['zone'].strip().split('\n'), expected_data) |
921 | ||
c1374bdb | 922 | def test_export_zone_text(self): |
6754ef71 | 923 | name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='') |
a83004d3 CH |
924 | # export it |
925 | r = self.session.get( | |
46d06a12 | 926 | self.url("/api/v1/servers/localhost/zones/" + name + "/export"), |
a83004d3 CH |
927 | headers={'accept': '*/*'} |
928 | ) | |
929 | data = r.text.strip().split("\n") | |
ba2a1254 DK |
930 | expected_data = [name + '\t3600\tIN\tNS\tns1.foo.com.', |
931 | name + '\t3600\tIN\tNS\tns2.foo.com.', | |
932 | name + '\t3600\tIN\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name + | |
1d6b70f9 | 933 | ' 0 10800 3600 604800 3600'] |
a83004d3 CH |
934 | self.assertEquals(data, expected_data) |
935 | ||
c1374bdb | 936 | def test_update_zone(self): |
6754ef71 | 937 | name, payload, zone = self.create_zone() |
bee2acae | 938 | name = payload['name'] |
d29d5db7 | 939 | # update, set as Master and enable SOA-EDIT-API |
7c0ba3d2 CH |
940 | payload = { |
941 | 'kind': 'Master', | |
c1374bdb | 942 | 'masters': ['192.0.2.1', '192.0.2.2'], |
6bb25159 MS |
943 | 'soa_edit_api': 'EPOCH', |
944 | 'soa_edit': 'EPOCH' | |
7c0ba3d2 CH |
945 | } |
946 | r = self.session.put( | |
46d06a12 | 947 | self.url("/api/v1/servers/localhost/zones/" + name), |
7c0ba3d2 CH |
948 | data=json.dumps(payload), |
949 | headers={'content-type': 'application/json'}) | |
f0e76cee CH |
950 | self.assert_success(r) |
951 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() | |
7c0ba3d2 CH |
952 | for k in payload.keys(): |
953 | self.assertIn(k, data) | |
954 | self.assertEquals(data[k], payload[k]) | |
d29d5db7 | 955 | # update, back to Native and empty(off) |
7c0ba3d2 | 956 | payload = { |
d29d5db7 | 957 | 'kind': 'Native', |
6bb25159 MS |
958 | 'soa_edit_api': '', |
959 | 'soa_edit': '' | |
7c0ba3d2 CH |
960 | } |
961 | r = self.session.put( | |
46d06a12 | 962 | self.url("/api/v1/servers/localhost/zones/" + name), |
7c0ba3d2 CH |
963 | data=json.dumps(payload), |
964 | headers={'content-type': 'application/json'}) | |
f0e76cee CH |
965 | self.assert_success(r) |
966 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() | |
7c0ba3d2 CH |
967 | for k in payload.keys(): |
968 | self.assertIn(k, data) | |
969 | self.assertEquals(data[k], payload[k]) | |
b3905a3d | 970 | |
c1374bdb | 971 | def test_zone_rr_update(self): |
6754ef71 | 972 | name, payload, zone = self.create_zone() |
b3905a3d | 973 | # do a replace (= update) |
d708640f | 974 | rrset = { |
b3905a3d CH |
975 | 'changetype': 'replace', |
976 | 'name': name, | |
8ce0dc75 | 977 | 'type': 'ns', |
6754ef71 | 978 | 'ttl': 3600, |
b3905a3d CH |
979 | 'records': [ |
980 | { | |
1d6b70f9 | 981 | "content": "ns1.bar.com.", |
cea26350 CH |
982 | "disabled": False |
983 | }, | |
984 | { | |
1d6b70f9 | 985 | "content": "ns2-disabled.bar.com.", |
cea26350 | 986 | "disabled": True |
b3905a3d CH |
987 | } |
988 | ] | |
989 | } | |
d708640f | 990 | payload = {'rrsets': [rrset]} |
b3905a3d | 991 | r = self.session.patch( |
46d06a12 | 992 | self.url("/api/v1/servers/localhost/zones/" + name), |
b3905a3d CH |
993 | data=json.dumps(payload), |
994 | headers={'content-type': 'application/json'}) | |
f0e76cee | 995 | self.assert_success(r) |
b3905a3d | 996 | # verify that (only) the new record is there |
f0e76cee | 997 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
6754ef71 | 998 | self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset['records']) |
b3905a3d | 999 | |
c1374bdb | 1000 | def test_zone_rr_update_mx(self): |
05cf6a71 | 1001 | # Important to test with MX records, as they have a priority field, which must end up in the content field. |
6754ef71 | 1002 | name, payload, zone = self.create_zone() |
41e3b10e | 1003 | # do a replace (= update) |
d708640f | 1004 | rrset = { |
41e3b10e CH |
1005 | 'changetype': 'replace', |
1006 | 'name': name, | |
1007 | 'type': 'MX', | |
6754ef71 | 1008 | 'ttl': 3600, |
41e3b10e CH |
1009 | 'records': [ |
1010 | { | |
1d6b70f9 | 1011 | "content": "10 mail.example.org.", |
41e3b10e CH |
1012 | "disabled": False |
1013 | } | |
1014 | ] | |
1015 | } | |
d708640f | 1016 | payload = {'rrsets': [rrset]} |
41e3b10e | 1017 | r = self.session.patch( |
46d06a12 | 1018 | self.url("/api/v1/servers/localhost/zones/" + name), |
41e3b10e CH |
1019 | data=json.dumps(payload), |
1020 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1021 | self.assert_success(r) |
41e3b10e | 1022 | # verify that (only) the new record is there |
f0e76cee | 1023 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
6754ef71 | 1024 | self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset['records']) |
d708640f | 1025 | |
81950930 CHB |
1026 | def test_zone_rr_update_invalid_mx(self): |
1027 | name, payload, zone = self.create_zone() | |
1028 | # do a replace (= update) | |
1029 | rrset = { | |
1030 | 'changetype': 'replace', | |
1031 | 'name': name, | |
1032 | 'type': 'MX', | |
1033 | 'ttl': 3600, | |
1034 | 'records': [ | |
1035 | { | |
1036 | "content": "10 mail@mx.example.org.", | |
1037 | "disabled": False | |
1038 | } | |
1039 | ] | |
1040 | } | |
1041 | payload = {'rrsets': [rrset]} | |
1042 | r = self.session.patch( | |
1043 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1044 | data=json.dumps(payload), | |
1045 | headers={'content-type': 'application/json'}) | |
1046 | self.assertEquals(r.status_code, 422) | |
97c8ea81 | 1047 | self.assertIn('non-hostname content', r.json()['error']) |
81950930 CHB |
1048 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1049 | self.assertIsNone(get_rrset(data, name, 'MX')) | |
1050 | ||
a53b24d0 CHB |
1051 | def test_zone_rr_update_opt(self): |
1052 | name, payload, zone = self.create_zone() | |
1053 | # do a replace (= update) | |
1054 | rrset = { | |
1055 | 'changetype': 'replace', | |
1056 | 'name': name, | |
1057 | 'type': 'OPT', | |
1058 | 'ttl': 3600, | |
1059 | 'records': [ | |
1060 | { | |
1061 | "content": "9", | |
1062 | "disabled": False | |
1063 | } | |
1064 | ] | |
1065 | } | |
1066 | payload = {'rrsets': [rrset]} | |
1067 | r = self.session.patch( | |
1068 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1069 | data=json.dumps(payload), | |
1070 | headers={'content-type': 'application/json'}) | |
1071 | self.assertEquals(r.status_code, 422) | |
1072 | self.assertIn('OPT: invalid type given', r.json()['error']) | |
1073 | ||
c1374bdb | 1074 | def test_zone_rr_update_multiple_rrsets(self): |
6754ef71 | 1075 | name, payload, zone = self.create_zone() |
d708640f CH |
1076 | rrset1 = { |
1077 | 'changetype': 'replace', | |
1078 | 'name': name, | |
1079 | 'type': 'NS', | |
6754ef71 | 1080 | 'ttl': 3600, |
d708640f CH |
1081 | 'records': [ |
1082 | { | |
6754ef71 | 1083 | |
1d6b70f9 | 1084 | "content": "ns9999.example.com.", |
d708640f CH |
1085 | "disabled": False |
1086 | } | |
1087 | ] | |
1088 | } | |
1089 | rrset2 = { | |
1090 | 'changetype': 'replace', | |
1091 | 'name': name, | |
1092 | 'type': 'MX', | |
6754ef71 | 1093 | 'ttl': 3600, |
d708640f CH |
1094 | 'records': [ |
1095 | { | |
1d6b70f9 | 1096 | "content": "10 mx444.example.com.", |
d708640f CH |
1097 | "disabled": False |
1098 | } | |
1099 | ] | |
1100 | } | |
1101 | payload = {'rrsets': [rrset1, rrset2]} | |
1102 | r = self.session.patch( | |
46d06a12 | 1103 | self.url("/api/v1/servers/localhost/zones/" + name), |
d708640f CH |
1104 | data=json.dumps(payload), |
1105 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1106 | self.assert_success(r) |
d708640f | 1107 | # verify that all rrsets have been updated |
f0e76cee | 1108 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
6754ef71 CH |
1109 | self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset1['records']) |
1110 | self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset2['records']) | |
41e3b10e | 1111 | |
e3675a8a CH |
1112 | def test_zone_rr_update_duplicate_record(self): |
1113 | name, payload, zone = self.create_zone() | |
1114 | rrset = { | |
1115 | 'changetype': 'replace', | |
1116 | 'name': name, | |
1117 | 'type': 'NS', | |
1118 | 'ttl': 3600, | |
1119 | 'records': [ | |
1120 | {"content": "ns9999.example.com.", "disabled": False}, | |
1121 | {"content": "ns9996.example.com.", "disabled": False}, | |
1122 | {"content": "ns9987.example.com.", "disabled": False}, | |
1123 | {"content": "ns9988.example.com.", "disabled": False}, | |
1124 | {"content": "ns9999.example.com.", "disabled": False}, | |
1125 | ] | |
1126 | } | |
1127 | payload = {'rrsets': [rrset]} | |
1128 | r = self.session.patch( | |
1129 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1130 | data=json.dumps(payload), | |
1131 | headers={'content-type': 'application/json'}) | |
1132 | self.assertEquals(r.status_code, 422) | |
1133 | self.assertIn('Duplicate record in RRset', r.json()['error']) | |
1134 | ||
90904988 PD |
1135 | def test_zone_rr_update_duplicate_rrset(self): |
1136 | name, payload, zone = self.create_zone() | |
1137 | rrset1 = { | |
1138 | 'changetype': 'replace', | |
1139 | 'name': name, | |
1140 | 'type': 'NS', | |
1141 | 'ttl': 3600, | |
1142 | 'records': [ | |
1143 | { | |
1144 | "content": "ns9999.example.com.", | |
1145 | "disabled": False | |
1146 | } | |
1147 | ] | |
1148 | } | |
1149 | rrset2 = { | |
1150 | 'changetype': 'replace', | |
1151 | 'name': name, | |
1152 | 'type': 'NS', | |
1153 | 'ttl': 3600, | |
1154 | 'records': [ | |
1155 | { | |
1156 | "content": "ns9998.example.com.", | |
1157 | "disabled": False | |
1158 | } | |
1159 | ] | |
1160 | } | |
1161 | payload = {'rrsets': [rrset1, rrset2]} | |
1162 | r = self.session.patch( | |
1163 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1164 | data=json.dumps(payload), | |
1165 | headers={'content-type': 'application/json'}) | |
1166 | self.assertEquals(r.status_code, 422) | |
1167 | self.assertIn('Duplicate RRset', r.json()['error']) | |
1168 | ||
c1374bdb | 1169 | def test_zone_rr_delete(self): |
6754ef71 | 1170 | name, payload, zone = self.create_zone() |
b3905a3d | 1171 | # do a delete of all NS records (these are created with the zone) |
d708640f | 1172 | rrset = { |
b3905a3d CH |
1173 | 'changetype': 'delete', |
1174 | 'name': name, | |
1175 | 'type': 'NS' | |
1176 | } | |
d708640f | 1177 | payload = {'rrsets': [rrset]} |
b3905a3d | 1178 | r = self.session.patch( |
46d06a12 | 1179 | self.url("/api/v1/servers/localhost/zones/" + name), |
b3905a3d CH |
1180 | data=json.dumps(payload), |
1181 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1182 | self.assert_success(r) |
b3905a3d | 1183 | # verify that the records are gone |
f0e76cee | 1184 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
6754ef71 | 1185 | self.assertIsNone(get_rrset(data, name, 'NS')) |
cea26350 | 1186 | |
c1374bdb | 1187 | def test_zone_disable_reenable(self): |
d29d5db7 | 1188 | # This also tests that SOA-EDIT-API works. |
6754ef71 | 1189 | name, payload, zone = self.create_zone(soa_edit_api='EPOCH') |
cea26350 | 1190 | # disable zone by disabling SOA |
d708640f | 1191 | rrset = { |
cea26350 CH |
1192 | 'changetype': 'replace', |
1193 | 'name': name, | |
1194 | 'type': 'SOA', | |
6754ef71 | 1195 | 'ttl': 3600, |
cea26350 CH |
1196 | 'records': [ |
1197 | { | |
1d6b70f9 | 1198 | "content": "ns1.bar.com. hostmaster.foo.org. 1 1 1 1 1", |
cea26350 CH |
1199 | "disabled": True |
1200 | } | |
1201 | ] | |
1202 | } | |
d708640f | 1203 | payload = {'rrsets': [rrset]} |
cea26350 | 1204 | r = self.session.patch( |
46d06a12 | 1205 | self.url("/api/v1/servers/localhost/zones/" + name), |
cea26350 CH |
1206 | data=json.dumps(payload), |
1207 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1208 | self.assert_success(r) |
d29d5db7 | 1209 | # check SOA serial has been edited |
f0e76cee CH |
1210 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1211 | soa_serial1 = get_first_rec(data, name, 'SOA')['content'].split()[2] | |
d29d5db7 CH |
1212 | self.assertNotEquals(soa_serial1, '1') |
1213 | # make sure domain is still in zone list (disabled SOA!) | |
46d06a12 | 1214 | r = self.session.get(self.url("/api/v1/servers/localhost/zones")) |
cea26350 CH |
1215 | domains = r.json() |
1216 | self.assertEquals(len([domain for domain in domains if domain['name'] == name]), 1) | |
d29d5db7 CH |
1217 | # sleep 1sec to ensure the EPOCH value changes for the next request |
1218 | time.sleep(1) | |
cea26350 | 1219 | # verify that modifying it still works |
d708640f CH |
1220 | rrset['records'][0]['disabled'] = False |
1221 | payload = {'rrsets': [rrset]} | |
cea26350 | 1222 | r = self.session.patch( |
46d06a12 | 1223 | self.url("/api/v1/servers/localhost/zones/" + name), |
cea26350 CH |
1224 | data=json.dumps(payload), |
1225 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1226 | self.assert_success(r) |
d29d5db7 | 1227 | # check SOA serial has been edited again |
f0e76cee CH |
1228 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1229 | soa_serial2 = get_first_rec(data, name, 'SOA')['content'].split()[2] | |
d29d5db7 CH |
1230 | self.assertNotEquals(soa_serial2, '1') |
1231 | self.assertNotEquals(soa_serial2, soa_serial1) | |
02945d9a | 1232 | |
c1374bdb | 1233 | def test_zone_rr_update_out_of_zone(self): |
6754ef71 | 1234 | name, payload, zone = self.create_zone() |
35f26cc5 | 1235 | # replace with qname mismatch |
d708640f | 1236 | rrset = { |
35f26cc5 | 1237 | 'changetype': 'replace', |
1d6b70f9 | 1238 | 'name': 'not-in-zone.', |
35f26cc5 | 1239 | 'type': 'NS', |
6754ef71 | 1240 | 'ttl': 3600, |
35f26cc5 CH |
1241 | 'records': [ |
1242 | { | |
1d6b70f9 | 1243 | "content": "ns1.bar.com.", |
35f26cc5 CH |
1244 | "disabled": False |
1245 | } | |
1246 | ] | |
1247 | } | |
d708640f | 1248 | payload = {'rrsets': [rrset]} |
35f26cc5 | 1249 | r = self.session.patch( |
46d06a12 | 1250 | self.url("/api/v1/servers/localhost/zones/" + name), |
35f26cc5 CH |
1251 | data=json.dumps(payload), |
1252 | headers={'content-type': 'application/json'}) | |
1253 | self.assertEquals(r.status_code, 422) | |
1254 | self.assertIn('out of zone', r.json()['error']) | |
1255 | ||
1d6b70f9 | 1256 | def test_zone_rr_update_restricted_chars(self): |
6754ef71 | 1257 | name, payload, zone = self.create_zone() |
1d6b70f9 CH |
1258 | # replace with qname mismatch |
1259 | rrset = { | |
1260 | 'changetype': 'replace', | |
1261 | 'name': 'test:' + name, | |
1262 | 'type': 'NS', | |
6754ef71 | 1263 | 'ttl': 3600, |
1d6b70f9 CH |
1264 | 'records': [ |
1265 | { | |
1d6b70f9 CH |
1266 | "content": "ns1.bar.com.", |
1267 | "disabled": False | |
1268 | } | |
1269 | ] | |
1270 | } | |
1271 | payload = {'rrsets': [rrset]} | |
1272 | r = self.session.patch( | |
1273 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1274 | data=json.dumps(payload), | |
1275 | headers={'content-type': 'application/json'}) | |
1276 | self.assertEquals(r.status_code, 422) | |
1277 | self.assertIn('contains unsupported characters', r.json()['error']) | |
1278 | ||
24cd86ca | 1279 | def test_rrset_unknown_type(self): |
6754ef71 | 1280 | name, payload, zone = self.create_zone() |
24cd86ca CH |
1281 | rrset = { |
1282 | 'changetype': 'replace', | |
1283 | 'name': name, | |
1284 | 'type': 'FAFAFA', | |
6754ef71 | 1285 | 'ttl': 3600, |
24cd86ca CH |
1286 | 'records': [ |
1287 | { | |
24cd86ca CH |
1288 | "content": "4.3.2.1", |
1289 | "disabled": False | |
1290 | } | |
1291 | ] | |
1292 | } | |
1293 | payload = {'rrsets': [rrset]} | |
46d06a12 | 1294 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), |
24cd86ca CH |
1295 | headers={'content-type': 'application/json'}) |
1296 | self.assertEquals(r.status_code, 422) | |
1297 | self.assertIn('unknown type', r.json()['error']) | |
1298 | ||
646bcd7d CH |
1299 | @parameterized.expand([ |
1300 | ('CNAME', ), | |
646bcd7d CH |
1301 | ]) |
1302 | def test_rrset_exclusive_and_other(self, qtype): | |
8560f36a CH |
1303 | name, payload, zone = self.create_zone() |
1304 | rrset = { | |
1305 | 'changetype': 'replace', | |
1306 | 'name': name, | |
646bcd7d | 1307 | 'type': qtype, |
8560f36a CH |
1308 | 'ttl': 3600, |
1309 | 'records': [ | |
1310 | { | |
1311 | "content": "example.org.", | |
1312 | "disabled": False | |
1313 | } | |
1314 | ] | |
1315 | } | |
1316 | payload = {'rrsets': [rrset]} | |
1317 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1318 | headers={'content-type': 'application/json'}) | |
1319 | self.assertEquals(r.status_code, 422) | |
646bcd7d | 1320 | self.assertIn('Conflicts with pre-existing RRset', r.json()['error']) |
8560f36a | 1321 | |
646bcd7d CH |
1322 | @parameterized.expand([ |
1323 | ('CNAME', ), | |
646bcd7d CH |
1324 | ]) |
1325 | def test_rrset_other_and_exclusive(self, qtype): | |
8560f36a CH |
1326 | name, payload, zone = self.create_zone() |
1327 | rrset = { | |
1328 | 'changetype': 'replace', | |
1329 | 'name': 'sub.'+name, | |
646bcd7d | 1330 | 'type': qtype, |
8560f36a CH |
1331 | 'ttl': 3600, |
1332 | 'records': [ | |
1333 | { | |
1334 | "content": "example.org.", | |
1335 | "disabled": False | |
1336 | } | |
1337 | ] | |
1338 | } | |
1339 | payload = {'rrsets': [rrset]} | |
1340 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1341 | headers={'content-type': 'application/json'}) | |
1342 | self.assert_success(r) | |
1343 | rrset = { | |
1344 | 'changetype': 'replace', | |
1345 | 'name': 'sub.'+name, | |
1346 | 'type': 'A', | |
1347 | 'ttl': 3600, | |
1348 | 'records': [ | |
1349 | { | |
1350 | "content": "1.2.3.4", | |
1351 | "disabled": False | |
1352 | } | |
1353 | ] | |
1354 | } | |
1355 | payload = {'rrsets': [rrset]} | |
1356 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1357 | headers={'content-type': 'application/json'}) | |
1358 | self.assertEquals(r.status_code, 422) | |
646bcd7d CH |
1359 | self.assertIn('Conflicts with pre-existing RRset', r.json()['error']) |
1360 | ||
1361 | @parameterized.expand([ | |
1362 | ('SOA', ['ns1.example.org. test@example.org. 10 10800 3600 604800 3600', 'ns2.example.org. test@example.org. 10 10800 3600 604800 3600']), | |
1363 | ('CNAME', ['01.example.org.', '02.example.org.']), | |
646bcd7d CH |
1364 | ]) |
1365 | def test_rrset_single_qtypes(self, qtype, contents): | |
8b1fa85d RG |
1366 | name, payload, zone = self.create_zone() |
1367 | rrset = { | |
1368 | 'changetype': 'replace', | |
1369 | 'name': 'sub.'+name, | |
646bcd7d | 1370 | 'type': qtype, |
8b1fa85d RG |
1371 | 'ttl': 3600, |
1372 | 'records': [ | |
1373 | { | |
646bcd7d | 1374 | "content": contents[0], |
8b1fa85d RG |
1375 | "disabled": False |
1376 | }, | |
1377 | { | |
646bcd7d | 1378 | "content": contents[1], |
8b1fa85d RG |
1379 | "disabled": False |
1380 | } | |
1381 | ] | |
1382 | } | |
1383 | payload = {'rrsets': [rrset]} | |
1384 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1385 | headers={'content-type': 'application/json'}) | |
1386 | self.assertEquals(r.status_code, 422) | |
646bcd7d | 1387 | self.assertIn('IN ' + qtype + ' has more than one record', r.json()['error']) |
8b1fa85d | 1388 | |
d2b117ec AT |
1389 | def test_rrset_zone_apex(self): |
1390 | name, payload, zone = self.create_zone() | |
1391 | rrset1 = { | |
1392 | 'changetype': 'replace', | |
1393 | 'name': name, | |
1394 | 'type': 'SOA', | |
1395 | 'ttl': 3600, | |
1396 | 'records': [ | |
1397 | { | |
1398 | "content": 'ns1.example.org. test@example.org. 10 10800 3600 604800 3600', | |
1399 | "disabled": False | |
1400 | }, | |
1401 | ] | |
1402 | } | |
1403 | rrset2 = { | |
1404 | 'changetype': 'replace', | |
1405 | 'name': name, | |
1406 | 'type': 'DNAME', | |
1407 | 'ttl': 3600, | |
1408 | 'records': [ | |
1409 | { | |
1410 | "content": 'example.com.', | |
1411 | "disabled": False | |
1412 | }, | |
1413 | ] | |
1414 | } | |
1415 | ||
1416 | payload = {'rrsets': [rrset1, rrset2]} | |
1417 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1418 | headers={'content-type': 'application/json'}) | |
1419 | self.assert_success(r) # user should be able to create DNAME at APEX as per RFC 6672 section 2.3 | |
1420 | ||
1421 | def test_rrset_ns_dname_exclude(self): | |
1422 | name, payload, zone = self.create_zone() | |
1423 | rrset = { | |
1424 | 'changetype': 'replace', | |
1425 | 'name': 'delegation.'+name, | |
1426 | 'type': 'NS', | |
1427 | 'ttl': 3600, | |
1428 | 'records': [ | |
1429 | { | |
1430 | "content": "ns.example.org.", | |
1431 | "disabled": False | |
1432 | } | |
1433 | ] | |
1434 | } | |
1435 | payload = {'rrsets': [rrset]} | |
1436 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1437 | headers={'content-type': 'application/json'}) | |
1438 | self.assert_success(r) | |
1439 | rrset = { | |
1440 | 'changetype': 'replace', | |
1441 | 'name': 'delegation.'+name, | |
1442 | 'type': 'DNAME', | |
1443 | 'ttl': 3600, | |
1444 | 'records': [ | |
1445 | { | |
7bc54205 | 1446 | "content": "example.com.", |
d2b117ec AT |
1447 | "disabled": False |
1448 | } | |
1449 | ] | |
1450 | } | |
1451 | payload = {'rrsets': [rrset]} | |
1452 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1453 | headers={'content-type': 'application/json'}) | |
1454 | self.assertEquals(r.status_code, 422) | |
1455 | self.assertIn('Cannot have both NS and DNAME except in zone apex', r.json()['error']) | |
1456 | ||
7bc54205 AT |
1457 | ## FIXME: Enable this when it's time for it |
1458 | # def test_rrset_dname_nothing_under(self): | |
1459 | # name, payload, zone = self.create_zone() | |
1460 | # rrset = { | |
1461 | # 'changetype': 'replace', | |
1462 | # 'name': 'delegation.'+name, | |
1463 | # 'type': 'DNAME', | |
1464 | # 'ttl': 3600, | |
1465 | # 'records': [ | |
1466 | # { | |
1467 | # "content": "example.com.", | |
1468 | # "disabled": False | |
1469 | # } | |
1470 | # ] | |
1471 | # } | |
1472 | # payload = {'rrsets': [rrset]} | |
1473 | # r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1474 | # headers={'content-type': 'application/json'}) | |
1475 | # self.assert_success(r) | |
1476 | # rrset = { | |
1477 | # 'changetype': 'replace', | |
1478 | # 'name': 'sub.delegation.'+name, | |
1479 | # 'type': 'A', | |
1480 | # 'ttl': 3600, | |
1481 | # 'records': [ | |
1482 | # { | |
1483 | # "content": "1.2.3.4", | |
1484 | # "disabled": False | |
1485 | # } | |
1486 | # ] | |
1487 | # } | |
1488 | # payload = {'rrsets': [rrset]} | |
1489 | # r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), | |
1490 | # headers={'content-type': 'application/json'}) | |
1491 | # self.assertEquals(r.status_code, 422) | |
1492 | # self.assertIn('You cannot have record(s) under CNAME/DNAME', r.json()['error']) | |
1493 | ||
1e5b9ab9 CH |
1494 | def test_create_zone_with_leading_space(self): |
1495 | # Actual regression. | |
6754ef71 | 1496 | name, payload, zone = self.create_zone() |
1e5b9ab9 CH |
1497 | rrset = { |
1498 | 'changetype': 'replace', | |
1499 | 'name': name, | |
1500 | 'type': 'A', | |
6754ef71 | 1501 | 'ttl': 3600, |
1e5b9ab9 CH |
1502 | 'records': [ |
1503 | { | |
1e5b9ab9 CH |
1504 | "content": " 4.3.2.1", |
1505 | "disabled": False | |
1506 | } | |
1507 | ] | |
1508 | } | |
1509 | payload = {'rrsets': [rrset]} | |
46d06a12 | 1510 | r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), |
1e5b9ab9 CH |
1511 | headers={'content-type': 'application/json'}) |
1512 | self.assertEquals(r.status_code, 422) | |
1513 | self.assertIn('Not in expected format', r.json()['error']) | |
1514 | ||
c1374bdb | 1515 | def test_zone_rr_delete_out_of_zone(self): |
6754ef71 | 1516 | name, payload, zone = self.create_zone() |
d708640f | 1517 | rrset = { |
35f26cc5 | 1518 | 'changetype': 'delete', |
1d6b70f9 | 1519 | 'name': 'not-in-zone.', |
35f26cc5 CH |
1520 | 'type': 'NS' |
1521 | } | |
d708640f | 1522 | payload = {'rrsets': [rrset]} |
35f26cc5 | 1523 | r = self.session.patch( |
46d06a12 | 1524 | self.url("/api/v1/servers/localhost/zones/" + name), |
35f26cc5 CH |
1525 | data=json.dumps(payload), |
1526 | headers={'content-type': 'application/json'}) | |
541bb91b | 1527 | print(r.content) |
f0e76cee | 1528 | self.assert_success(r) # succeed so users can fix their wrong, old data |
35f26cc5 | 1529 | |
37663c3b | 1530 | def test_zone_delete(self): |
6754ef71 | 1531 | name, payload, zone = self.create_zone() |
46d06a12 | 1532 | r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name)) |
37663c3b CH |
1533 | self.assertEquals(r.status_code, 204) |
1534 | self.assertNotIn('Content-Type', r.headers) | |
1535 | ||
c1374bdb | 1536 | def test_zone_comment_create(self): |
6754ef71 | 1537 | name, payload, zone = self.create_zone() |
d708640f | 1538 | rrset = { |
6cc98ddf CH |
1539 | 'changetype': 'replace', |
1540 | 'name': name, | |
1541 | 'type': 'NS', | |
6754ef71 | 1542 | 'ttl': 3600, |
6cc98ddf CH |
1543 | 'comments': [ |
1544 | { | |
1545 | 'account': 'test1', | |
1546 | 'content': 'blah blah', | |
1547 | }, | |
1548 | { | |
1549 | 'account': 'test2', | |
1550 | 'content': 'blah blah bleh', | |
1551 | } | |
1552 | ] | |
1553 | } | |
d708640f | 1554 | payload = {'rrsets': [rrset]} |
6cc98ddf | 1555 | r = self.session.patch( |
46d06a12 | 1556 | self.url("/api/v1/servers/localhost/zones/" + name), |
6cc98ddf CH |
1557 | data=json.dumps(payload), |
1558 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1559 | self.assert_success(r) |
6cc98ddf CH |
1560 | # make sure the comments have been set, and that the NS |
1561 | # records are still present | |
f0e76cee CH |
1562 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1563 | serverset = get_rrset(data, name, 'NS') | |
541bb91b | 1564 | print(serverset) |
6754ef71 CH |
1565 | self.assertNotEquals(serverset['records'], []) |
1566 | self.assertNotEquals(serverset['comments'], []) | |
6cc98ddf | 1567 | # verify that modified_at has been set by pdns |
6754ef71 | 1568 | self.assertNotEquals([c for c in serverset['comments']][0]['modified_at'], 0) |
0d7f3c75 CH |
1569 | # verify that TTL is correct (regression test) |
1570 | self.assertEquals(serverset['ttl'], 3600) | |
6cc98ddf | 1571 | |
c1374bdb | 1572 | def test_zone_comment_delete(self): |
6cc98ddf | 1573 | # Test: Delete ONLY comments. |
6754ef71 | 1574 | name, payload, zone = self.create_zone() |
d708640f | 1575 | rrset = { |
6cc98ddf CH |
1576 | 'changetype': 'replace', |
1577 | 'name': name, | |
1578 | 'type': 'NS', | |
1579 | 'comments': [] | |
1580 | } | |
d708640f | 1581 | payload = {'rrsets': [rrset]} |
6cc98ddf | 1582 | r = self.session.patch( |
46d06a12 | 1583 | self.url("/api/v1/servers/localhost/zones/" + name), |
6cc98ddf CH |
1584 | data=json.dumps(payload), |
1585 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1586 | self.assert_success(r) |
6cc98ddf | 1587 | # make sure the NS records are still present |
f0e76cee CH |
1588 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1589 | serverset = get_rrset(data, name, 'NS') | |
541bb91b | 1590 | print(serverset) |
6754ef71 CH |
1591 | self.assertNotEquals(serverset['records'], []) |
1592 | self.assertEquals(serverset['comments'], []) | |
6cc98ddf | 1593 | |
1148587f CH |
1594 | def test_zone_comment_out_of_range_modified_at(self): |
1595 | # Test if comments on an rrset stay intact if the rrset is replaced | |
1596 | name, payload, zone = self.create_zone() | |
1597 | rrset = { | |
1598 | 'changetype': 'replace', | |
1599 | 'name': name, | |
1600 | 'type': 'NS', | |
1601 | 'comments': [ | |
1602 | { | |
1603 | 'account': 'test1', | |
1604 | 'content': 'oh hi there', | |
1605 | 'modified_at': '4294967297' | |
1606 | } | |
1607 | ] | |
1608 | } | |
1609 | payload = {'rrsets': [rrset]} | |
1610 | r = self.session.patch( | |
1611 | self.url("/api/v1/servers/localhost/zones/" + name), | |
1612 | data=json.dumps(payload), | |
1613 | headers={'content-type': 'application/json'}) | |
1614 | self.assertEquals(r.status_code, 422) | |
1615 | self.assertIn("Value for key 'modified_at' is out of range", r.json()['error']) | |
1616 | ||
c1374bdb | 1617 | def test_zone_comment_stay_intact(self): |
6cc98ddf | 1618 | # Test if comments on an rrset stay intact if the rrset is replaced |
6754ef71 | 1619 | name, payload, zone = self.create_zone() |
6cc98ddf | 1620 | # create a comment |
d708640f | 1621 | rrset = { |
6cc98ddf CH |
1622 | 'changetype': 'replace', |
1623 | 'name': name, | |
1624 | 'type': 'NS', | |
1625 | 'comments': [ | |
1626 | { | |
1627 | 'account': 'test1', | |
1628 | 'content': 'oh hi there', | |
2696eea0 | 1629 | 'modified_at': 1111 |
6cc98ddf CH |
1630 | } |
1631 | ] | |
1632 | } | |
d708640f | 1633 | payload = {'rrsets': [rrset]} |
6cc98ddf | 1634 | r = self.session.patch( |
46d06a12 | 1635 | self.url("/api/v1/servers/localhost/zones/" + name), |
6cc98ddf CH |
1636 | data=json.dumps(payload), |
1637 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1638 | self.assert_success(r) |
6cc98ddf | 1639 | # replace rrset records |
d708640f | 1640 | rrset2 = { |
6cc98ddf CH |
1641 | 'changetype': 'replace', |
1642 | 'name': name, | |
1643 | 'type': 'NS', | |
6754ef71 | 1644 | 'ttl': 3600, |
6cc98ddf CH |
1645 | 'records': [ |
1646 | { | |
1d6b70f9 | 1647 | "content": "ns1.bar.com.", |
6cc98ddf CH |
1648 | "disabled": False |
1649 | } | |
1650 | ] | |
1651 | } | |
d708640f | 1652 | payload2 = {'rrsets': [rrset2]} |
6cc98ddf | 1653 | r = self.session.patch( |
46d06a12 | 1654 | self.url("/api/v1/servers/localhost/zones/" + name), |
6cc98ddf CH |
1655 | data=json.dumps(payload2), |
1656 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1657 | self.assert_success(r) |
6cc98ddf | 1658 | # make sure the comments still exist |
f0e76cee CH |
1659 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json() |
1660 | serverset = get_rrset(data, name, 'NS') | |
541bb91b | 1661 | print(serverset) |
6754ef71 CH |
1662 | self.assertEquals(serverset['records'], rrset2['records']) |
1663 | self.assertEquals(serverset['comments'], rrset['comments']) | |
6cc98ddf | 1664 | |
3fe7c7d6 CH |
1665 | def test_zone_auto_ptr_ipv4_create(self): |
1666 | revzone = '4.2.192.in-addr.arpa.' | |
1667 | _, _, revzonedata = self.create_zone(name=revzone) | |
1668 | name = unique_zone_name() | |
1669 | rrset = { | |
1670 | "name": name, | |
1671 | "type": "A", | |
1672 | "ttl": 3600, | |
1673 | "records": [{ | |
1674 | "content": "192.2.4.44", | |
1675 | "disabled": False, | |
1676 | "set-ptr": True, | |
1677 | }], | |
1678 | } | |
1679 | name, payload, data = self.create_zone(name=name, rrsets=[rrset]) | |
1680 | del rrset['records'][0]['set-ptr'] | |
1681 | self.assertEquals(get_rrset(data, name, 'A')['records'], rrset['records']) | |
1682 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json() | |
1683 | revsets = [s for s in r['rrsets'] if s['type'] == 'PTR'] | |
541bb91b | 1684 | print(revsets) |
3fe7c7d6 CH |
1685 | self.assertEquals(revsets, [{ |
1686 | u'name': u'44.4.2.192.in-addr.arpa.', | |
1687 | u'ttl': 3600, | |
1688 | u'type': u'PTR', | |
1689 | u'comments': [], | |
1690 | u'records': [{ | |
1691 | u'content': name, | |
1692 | u'disabled': False, | |
1693 | }], | |
1694 | }]) | |
1695 | # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher. | |
1696 | self.assertGreater(r['serial'], revzonedata['serial']) | |
1697 | ||
1698 | def test_zone_auto_ptr_ipv4_update(self): | |
1d6b70f9 | 1699 | revzone = '0.2.192.in-addr.arpa.' |
a41c038a | 1700 | _, _, revzonedata = self.create_zone(name=revzone) |
6754ef71 | 1701 | name, payload, zone = self.create_zone() |
d708640f | 1702 | rrset = { |
d1587ceb CH |
1703 | 'changetype': 'replace', |
1704 | 'name': name, | |
1705 | 'type': 'A', | |
6754ef71 | 1706 | 'ttl': 3600, |
d1587ceb CH |
1707 | 'records': [ |
1708 | { | |
d1587ceb CH |
1709 | "content": '192.2.0.2', |
1710 | "disabled": False, | |
1711 | "set-ptr": True | |
1712 | } | |
1713 | ] | |
1714 | } | |
d708640f | 1715 | payload = {'rrsets': [rrset]} |
d1587ceb | 1716 | r = self.session.patch( |
46d06a12 | 1717 | self.url("/api/v1/servers/localhost/zones/" + name), |
d1587ceb CH |
1718 | data=json.dumps(payload), |
1719 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1720 | self.assert_success(r) |
a41c038a CH |
1721 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json() |
1722 | revsets = [s for s in r['rrsets'] if s['type'] == 'PTR'] | |
541bb91b | 1723 | print(revsets) |
6754ef71 CH |
1724 | self.assertEquals(revsets, [{ |
1725 | u'name': u'2.0.2.192.in-addr.arpa.', | |
d1587ceb | 1726 | u'ttl': 3600, |
d1587ceb | 1727 | u'type': u'PTR', |
6754ef71 CH |
1728 | u'comments': [], |
1729 | u'records': [{ | |
1730 | u'content': name, | |
1731 | u'disabled': False, | |
1732 | }], | |
d1587ceb | 1733 | }]) |
a41c038a CH |
1734 | # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher. |
1735 | self.assertGreater(r['serial'], revzonedata['serial']) | |
d1587ceb | 1736 | |
3fe7c7d6 | 1737 | def test_zone_auto_ptr_ipv6_update(self): |
d1587ceb | 1738 | # 2001:DB8::bb:aa |
1d6b70f9 | 1739 | revzone = '8.b.d.0.1.0.0.2.ip6.arpa.' |
a41c038a | 1740 | _, _, revzonedata = self.create_zone(name=revzone) |
6754ef71 | 1741 | name, payload, zone = self.create_zone() |
d708640f | 1742 | rrset = { |
d1587ceb CH |
1743 | 'changetype': 'replace', |
1744 | 'name': name, | |
1745 | 'type': 'AAAA', | |
6754ef71 | 1746 | 'ttl': 3600, |
d1587ceb CH |
1747 | 'records': [ |
1748 | { | |
d1587ceb CH |
1749 | "content": '2001:DB8::bb:aa', |
1750 | "disabled": False, | |
1751 | "set-ptr": True | |
1752 | } | |
1753 | ] | |
1754 | } | |
d708640f | 1755 | payload = {'rrsets': [rrset]} |
d1587ceb | 1756 | r = self.session.patch( |
46d06a12 | 1757 | self.url("/api/v1/servers/localhost/zones/" + name), |
d1587ceb CH |
1758 | data=json.dumps(payload), |
1759 | headers={'content-type': 'application/json'}) | |
f0e76cee | 1760 | self.assert_success(r) |
a41c038a CH |
1761 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json() |
1762 | revsets = [s for s in r['rrsets'] if s['type'] == 'PTR'] | |
541bb91b | 1763 | print(revsets) |
6754ef71 CH |
1764 | self.assertEquals(revsets, [{ |
1765 | u'name': u'a.a.0.0.b.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.', | |
d1587ceb | 1766 | u'ttl': 3600, |
d1587ceb | 1767 | u'type': u'PTR', |
6754ef71 CH |
1768 | u'comments': [], |
1769 | u'records': [{ | |
1770 | u'content': name, | |
1771 | u'disabled': False, | |
1772 | }], | |
d1587ceb | 1773 | }]) |
a41c038a CH |
1774 | # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher. |
1775 | self.assertGreater(r['serial'], revzonedata['serial']) | |
d1587ceb | 1776 | |
c1374bdb | 1777 | def test_search_rr_exact_zone(self): |
b1902fab | 1778 | name = unique_zone_name() |
1d6b70f9 CH |
1779 | self.create_zone(name=name, serial=22, soa_edit_api='') |
1780 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.'))) | |
c1374bdb | 1781 | self.assert_success_json(r) |
541bb91b | 1782 | print(r.json()) |
1d6b70f9 CH |
1783 | self.assertEquals(r.json(), [ |
1784 | {u'object_type': u'zone', u'name': name, u'zone_id': name}, | |
1d6b70f9 CH |
1785 | {u'content': u'ns1.example.com.', |
1786 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, | |
1787 | u'ttl': 3600, u'type': u'NS', u'name': name}, | |
1788 | {u'content': u'ns2.example.com.', | |
1789 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, | |
1790 | u'ttl': 3600, u'type': u'NS', u'name': name}, | |
f2d6dcc0 | 1791 | {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600', |
45250285 JE |
1792 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, |
1793 | u'ttl': 3600, u'type': u'SOA', u'name': name}, | |
1794 | ]) | |
1795 | ||
1796 | def test_search_rr_exact_zone_filter_type_zone(self): | |
1797 | name = unique_zone_name() | |
1798 | data_type = "zone" | |
1799 | self.create_zone(name=name, serial=22, soa_edit_api='') | |
0bd91de6 | 1800 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.') + "&object_type=" + data_type)) |
45250285 JE |
1801 | self.assert_success_json(r) |
1802 | print(r.json()) | |
1803 | self.assertEquals(r.json(), [ | |
1804 | {u'object_type': u'zone', u'name': name, u'zone_id': name}, | |
1805 | ]) | |
1806 | ||
1807 | def test_search_rr_exact_zone_filter_type_record(self): | |
1808 | name = unique_zone_name() | |
1809 | data_type = "record" | |
1810 | self.create_zone(name=name, serial=22, soa_edit_api='') | |
0bd91de6 | 1811 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.') + "&object_type=" + data_type)) |
45250285 JE |
1812 | self.assert_success_json(r) |
1813 | print(r.json()) | |
1814 | self.assertEquals(r.json(), [ | |
1815 | {u'content': u'ns1.example.com.', | |
1816 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, | |
1817 | u'ttl': 3600, u'type': u'NS', u'name': name}, | |
1818 | {u'content': u'ns2.example.com.', | |
1819 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, | |
1820 | u'ttl': 3600, u'type': u'NS', u'name': name}, | |
1821 | {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600', | |
f2d6dcc0 RG |
1822 | u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False, |
1823 | u'ttl': 3600, u'type': u'SOA', u'name': name}, | |
1d6b70f9 | 1824 | ]) |
b1902fab | 1825 | |
c1374bdb | 1826 | def test_search_rr_substring(self): |
541bb91b CH |
1827 | name = unique_zone_name() |
1828 | search = name[5:-5] | |
b1902fab | 1829 | self.create_zone(name=name) |
541bb91b | 1830 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*%s*" % search)) |
c1374bdb | 1831 | self.assert_success_json(r) |
541bb91b | 1832 | print(r.json()) |
b1902fab | 1833 | # should return zone, SOA, ns1, ns2 |
60a8e825 | 1834 | self.assertEquals(len(r.json()), 4) |
b1902fab | 1835 | |
c1374bdb | 1836 | def test_search_rr_case_insensitive(self): |
541bb91b | 1837 | name = unique_zone_name()+'testsuffix.' |
57cb86d8 | 1838 | self.create_zone(name=name) |
541bb91b | 1839 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*testSUFFIX*")) |
c1374bdb | 1840 | self.assert_success_json(r) |
541bb91b | 1841 | print(r.json()) |
57cb86d8 | 1842 | # should return zone, SOA, ns1, ns2 |
60a8e825 | 1843 | self.assertEquals(len(r.json()), 4) |
57cb86d8 | 1844 | |
7cbc5255 | 1845 | def test_search_after_rectify_with_ent(self): |
541bb91b CH |
1846 | name = unique_zone_name() |
1847 | search = name.split('.')[0] | |
7cbc5255 CH |
1848 | rrset = { |
1849 | "name": 'sub.sub.' + name, | |
1850 | "type": "A", | |
1851 | "ttl": 3600, | |
1852 | "records": [{ | |
1853 | "content": "4.3.2.1", | |
1854 | "disabled": False, | |
1855 | }], | |
1856 | } | |
1857 | self.create_zone(name=name, rrsets=[rrset]) | |
1858 | pdnsutil_rectify(name) | |
541bb91b | 1859 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*%s*" % search)) |
7cbc5255 | 1860 | self.assert_success_json(r) |
541bb91b | 1861 | print(r.json()) |
7cbc5255 CH |
1862 | # should return zone, SOA, ns1, ns2, sub.sub A (but not the ENT) |
1863 | self.assertEquals(len(r.json()), 5) | |
1864 | ||
b8cd24cc SH |
1865 | def test_default_api_rectify(self): |
1866 | name = unique_zone_name() | |
1867 | search = name.split('.')[0] | |
1868 | rrsets = [ | |
1869 | { | |
1870 | "name": 'a.' + name, | |
1871 | "type": "AAAA", | |
1872 | "ttl": 3600, | |
1873 | "records": [{ | |
1874 | "content": "2001:DB8::1", | |
1875 | "disabled": False, | |
1876 | }], | |
1877 | }, | |
1878 | { | |
1879 | "name": 'b.' + name, | |
1880 | "type": "AAAA", | |
1881 | "ttl": 3600, | |
1882 | "records": [{ | |
1883 | "content": "2001:DB8::2", | |
1884 | "disabled": False, | |
1885 | }], | |
1886 | }, | |
1887 | ] | |
1888 | self.create_zone(name=name, rrsets=rrsets, dnssec=True, nsec3param='1 0 1 ab') | |
1889 | dbrecs = get_db_records(name, 'AAAA') | |
1890 | self.assertIsNotNone(dbrecs[0]['ordername']) | |
1891 | ||
1892 | def test_override_api_rectify(self): | |
1893 | name = unique_zone_name() | |
1894 | search = name.split('.')[0] | |
1895 | rrsets = [ | |
1896 | { | |
1897 | "name": 'a.' + name, | |
1898 | "type": "AAAA", | |
1899 | "ttl": 3600, | |
1900 | "records": [{ | |
1901 | "content": "2001:DB8::1", | |
1902 | "disabled": False, | |
1903 | }], | |
1904 | }, | |
1905 | { | |
1906 | "name": 'b.' + name, | |
1907 | "type": "AAAA", | |
1908 | "ttl": 3600, | |
1909 | "records": [{ | |
1910 | "content": "2001:DB8::2", | |
1911 | "disabled": False, | |
1912 | }], | |
1913 | }, | |
1914 | ] | |
1915 | self.create_zone(name=name, rrsets=rrsets, api_rectify=False, dnssec=True, nsec3param='1 0 1 ab') | |
1916 | dbrecs = get_db_records(name, 'AAAA') | |
1917 | self.assertIsNone(dbrecs[0]['ordername']) | |
1918 | ||
03b1cc25 | 1919 | def test_cname_at_ent_place(self): |
f04b32e4 | 1920 | name, payload, zone = self.create_zone(dnssec=True, api_rectify=True) |
03b1cc25 CH |
1921 | rrset = { |
1922 | 'changetype': 'replace', | |
1923 | 'name': 'sub2.sub1.' + name, | |
1924 | 'type': "A", | |
1925 | 'ttl': 3600, | |
1926 | 'records': [{ | |
1927 | 'content': "4.3.2.1", | |
1928 | 'disabled': False, | |
1929 | }], | |
1930 | } | |
1931 | payload = {'rrsets': [rrset]} | |
1932 | r = self.session.patch( | |
1933 | self.url("/api/v1/servers/localhost/zones/" + zone['id']), | |
1934 | data=json.dumps(payload), | |
1935 | headers={'content-type': 'application/json'}) | |
1936 | self.assertEquals(r.status_code, 204) | |
1937 | rrset = { | |
1938 | 'changetype': 'replace', | |
1939 | 'name': 'sub1.' + name, | |
1940 | 'type': "CNAME", | |
1941 | 'ttl': 3600, | |
1942 | 'records': [{ | |
1943 | 'content': "www.example.org.", | |
1944 | 'disabled': False, | |
1945 | }], | |
1946 | } | |
1947 | payload = {'rrsets': [rrset]} | |
1948 | r = self.session.patch( | |
1949 | self.url("/api/v1/servers/localhost/zones/" + zone['id']), | |
1950 | data=json.dumps(payload), | |
1951 | headers={'content-type': 'application/json'}) | |
1952 | self.assertEquals(r.status_code, 204) | |
1953 | ||
986e4858 PL |
1954 | def test_rrset_parameter_post_false(self): |
1955 | name = unique_zone_name() | |
1956 | payload = { | |
1957 | 'name': name, | |
1958 | 'kind': 'Native', | |
1959 | 'nameservers': ['ns1.example.com.', 'ns2.example.com.'] | |
1960 | } | |
1961 | r = self.session.post( | |
1962 | self.url("/api/v1/servers/localhost/zones?rrsets=false"), | |
1963 | data=json.dumps(payload), | |
1964 | headers={'content-type': 'application/json'}) | |
541bb91b | 1965 | print(r.json()) |
986e4858 PL |
1966 | self.assert_success_json(r) |
1967 | self.assertEquals(r.status_code, 201) | |
1968 | self.assertEquals(r.json().get('rrsets'), None) | |
1969 | ||
1970 | def test_rrset_false_parameter(self): | |
1971 | name = unique_zone_name() | |
1972 | self.create_zone(name=name, kind='Native') | |
1973 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=false")) | |
1974 | self.assert_success_json(r) | |
541bb91b | 1975 | print(r.json()) |
986e4858 PL |
1976 | self.assertEquals(r.json().get('rrsets'), None) |
1977 | ||
1978 | def test_rrset_true_parameter(self): | |
1979 | name = unique_zone_name() | |
1980 | self.create_zone(name=name, kind='Native') | |
1981 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=true")) | |
1982 | self.assert_success_json(r) | |
541bb91b | 1983 | print(r.json()) |
986e4858 PL |
1984 | self.assertEquals(len(r.json().get('rrsets')), 2) |
1985 | ||
1986 | def test_wrong_rrset_parameter(self): | |
1987 | name = unique_zone_name() | |
1988 | self.create_zone(name=name, kind='Native') | |
1989 | r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=foobar")) | |
1990 | self.assertEquals(r.status_code, 422) | |
1991 | self.assertIn("'rrsets' request parameter value 'foobar' is not supported", r.json()['error']) | |
1992 | ||
dc30b8fd PL |
1993 | def test_put_master_tsig_key_ids_non_existent(self): |
1994 | name = unique_zone_name() | |
1995 | keyname = unique_zone_name().split('.')[0] | |
1996 | self.create_zone(name=name, kind='Native') | |
1997 | payload = { | |
1998 | 'master_tsig_key_ids': [keyname] | |
1999 | } | |
2000 | r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name), | |
2001 | data=json.dumps(payload), | |
2002 | headers={'content-type': 'application/json'}) | |
2003 | self.assertEquals(r.status_code, 422) | |
2004 | self.assertIn('A TSIG key with the name', r.json()['error']) | |
2005 | ||
2006 | def test_put_slave_tsig_key_ids_non_existent(self): | |
2007 | name = unique_zone_name() | |
2008 | keyname = unique_zone_name().split('.')[0] | |
2009 | self.create_zone(name=name, kind='Native') | |
2010 | payload = { | |
2011 | 'slave_tsig_key_ids': [keyname] | |
2012 | } | |
2013 | r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name), | |
2014 | data=json.dumps(payload), | |
2015 | headers={'content-type': 'application/json'}) | |
2016 | self.assertEquals(r.status_code, 422) | |
2017 | self.assertIn('A TSIG key with the name', r.json()['error']) | |
2018 | ||
02945d9a | 2019 | |
406497f5 CH |
2020 | @unittest.skipIf(not is_auth(), "Not applicable") |
2021 | class AuthRootZone(ApiTestCase, AuthZonesHelperMixin): | |
2022 | ||
2023 | def setUp(self): | |
2024 | super(AuthRootZone, self).setUp() | |
2025 | # zone name is not unique, so delete the zone before each individual test. | |
46d06a12 | 2026 | self.session.delete(self.url("/api/v1/servers/localhost/zones/=2E")) |
406497f5 CH |
2027 | |
2028 | def test_create_zone(self): | |
6754ef71 | 2029 | name, payload, data = self.create_zone(name='.', serial=22, soa_edit_api='') |
406497f5 CH |
2030 | for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'): |
2031 | self.assertIn(k, data) | |
2032 | if k in payload: | |
2033 | self.assertEquals(data[k], payload[k]) | |
406497f5 | 2034 | # validate generated SOA |
6754ef71 | 2035 | rec = get_first_rec(data, '.', 'SOA') |
406497f5 | 2036 | self.assertEquals( |
6754ef71 | 2037 | rec['content'], |
1d6b70f9 | 2038 | "a.misconfigured.powerdns.server. hostmaster. " + str(payload['serial']) + |
406497f5 CH |
2039 | " 10800 3600 604800 3600" |
2040 | ) | |
2041 | # Regression test: verify zone list works | |
46d06a12 | 2042 | zonelist = self.session.get(self.url("/api/v1/servers/localhost/zones")).json() |
541bb91b | 2043 | print("zonelist:", zonelist) |
406497f5 CH |
2044 | self.assertIn(payload['name'], [zone['name'] for zone in zonelist]) |
2045 | # Also test that fetching the zone works. | |
541bb91b | 2046 | print("id:", data['id']) |
406497f5 | 2047 | self.assertEquals(data['id'], '=2E') |
46d06a12 | 2048 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json() |
541bb91b | 2049 | print("zone (fetched):", data) |
406497f5 CH |
2050 | for k in ('name', 'kind'): |
2051 | self.assertIn(k, data) | |
2052 | self.assertEquals(data[k], payload[k]) | |
6754ef71 | 2053 | self.assertEqual(data['rrsets'][0]['name'], '.') |
406497f5 CH |
2054 | |
2055 | def test_update_zone(self): | |
6754ef71 | 2056 | name, payload, zone = self.create_zone(name='.') |
406497f5 CH |
2057 | zone_id = '=2E' |
2058 | # update, set as Master and enable SOA-EDIT-API | |
2059 | payload = { | |
2060 | 'kind': 'Master', | |
2061 | 'masters': ['192.0.2.1', '192.0.2.2'], | |
2062 | 'soa_edit_api': 'EPOCH', | |
2063 | 'soa_edit': 'EPOCH' | |
2064 | } | |
2065 | r = self.session.put( | |
46d06a12 | 2066 | self.url("/api/v1/servers/localhost/zones/" + zone_id), |
406497f5 CH |
2067 | data=json.dumps(payload), |
2068 | headers={'content-type': 'application/json'}) | |
f0e76cee CH |
2069 | self.assert_success(r) |
2070 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json() | |
406497f5 CH |
2071 | for k in payload.keys(): |
2072 | self.assertIn(k, data) | |
2073 | self.assertEquals(data[k], payload[k]) | |
2074 | # update, back to Native and empty(off) | |
2075 | payload = { | |
2076 | 'kind': 'Native', | |
2077 | 'soa_edit_api': '', | |
2078 | 'soa_edit': '' | |
2079 | } | |
2080 | r = self.session.put( | |
46d06a12 | 2081 | self.url("/api/v1/servers/localhost/zones/" + zone_id), |
406497f5 CH |
2082 | data=json.dumps(payload), |
2083 | headers={'content-type': 'application/json'}) | |
f0e76cee CH |
2084 | self.assert_success(r) |
2085 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json() | |
406497f5 CH |
2086 | for k in payload.keys(): |
2087 | self.assertIn(k, data) | |
2088 | self.assertEquals(data[k], payload[k]) | |
2089 | ||
2090 | ||
c1374bdb | 2091 | @unittest.skipIf(not is_recursor(), "Not applicable") |
02945d9a CH |
2092 | class RecursorZones(ApiTestCase): |
2093 | ||
37bc3d01 CH |
2094 | def create_zone(self, name=None, kind=None, rd=False, servers=None): |
2095 | if name is None: | |
2096 | name = unique_zone_name() | |
2097 | if servers is None: | |
2098 | servers = [] | |
02945d9a | 2099 | payload = { |
37bc3d01 CH |
2100 | 'name': name, |
2101 | 'kind': kind, | |
2102 | 'servers': servers, | |
2103 | 'recursion_desired': rd | |
02945d9a CH |
2104 | } |
2105 | r = self.session.post( | |
46d06a12 | 2106 | self.url("/api/v1/servers/localhost/zones"), |
02945d9a CH |
2107 | data=json.dumps(payload), |
2108 | headers={'content-type': 'application/json'}) | |
c1374bdb CH |
2109 | self.assert_success_json(r) |
2110 | return payload, r.json() | |
37bc3d01 | 2111 | |
c1374bdb | 2112 | def test_create_auth_zone(self): |
37bc3d01 | 2113 | payload, data = self.create_zone(kind='Native') |
02945d9a CH |
2114 | for k in payload.keys(): |
2115 | self.assertEquals(data[k], payload[k]) | |
2116 | ||
1d6b70f9 | 2117 | def test_create_zone_no_name(self): |
1d6b70f9 CH |
2118 | payload = { |
2119 | 'name': '', | |
2120 | 'kind': 'Native', | |
2121 | 'servers': ['8.8.8.8'], | |
2122 | 'recursion_desired': False, | |
2123 | } | |
541bb91b | 2124 | print(payload) |
1d6b70f9 CH |
2125 | r = self.session.post( |
2126 | self.url("/api/v1/servers/localhost/zones"), | |
2127 | data=json.dumps(payload), | |
2128 | headers={'content-type': 'application/json'}) | |
2129 | self.assertEquals(r.status_code, 422) | |
2130 | self.assertIn('is not canonical', r.json()['error']) | |
2131 | ||
c1374bdb | 2132 | def test_create_forwarded_zone(self): |
37bc3d01 | 2133 | payload, data = self.create_zone(kind='Forwarded', rd=False, servers=['8.8.8.8']) |
02945d9a CH |
2134 | # return values are normalized |
2135 | payload['servers'][0] += ':53' | |
02945d9a CH |
2136 | for k in payload.keys(): |
2137 | self.assertEquals(data[k], payload[k]) | |
2138 | ||
c1374bdb | 2139 | def test_create_forwarded_rd_zone(self): |
1d6b70f9 | 2140 | payload, data = self.create_zone(name='google.com.', kind='Forwarded', rd=True, servers=['8.8.8.8']) |
02945d9a CH |
2141 | # return values are normalized |
2142 | payload['servers'][0] += ':53' | |
02945d9a CH |
2143 | for k in payload.keys(): |
2144 | self.assertEquals(data[k], payload[k]) | |
2145 | ||
c1374bdb | 2146 | def test_create_auth_zone_with_symbols(self): |
37bc3d01 | 2147 | payload, data = self.create_zone(name='foo/bar.'+unique_zone_name(), kind='Native') |
1dbe38ba | 2148 | expected_id = (payload['name'].replace('/', '=2F')) |
02945d9a CH |
2149 | for k in payload.keys(): |
2150 | self.assertEquals(data[k], payload[k]) | |
2151 | self.assertEquals(data['id'], expected_id) | |
e2367534 | 2152 | |
c1374bdb | 2153 | def test_rename_auth_zone(self): |
37bc3d01 | 2154 | payload, data = self.create_zone(kind='Native') |
1d6b70f9 | 2155 | name = payload['name'] |
e2367534 CH |
2156 | # now rename it |
2157 | payload = { | |
2158 | 'name': 'renamed-'+name, | |
2159 | 'kind': 'Native', | |
2160 | 'recursion_desired': False | |
2161 | } | |
2162 | r = self.session.put( | |
46d06a12 | 2163 | self.url("/api/v1/servers/localhost/zones/" + name), |
e2367534 CH |
2164 | data=json.dumps(payload), |
2165 | headers={'content-type': 'application/json'}) | |
f0e76cee CH |
2166 | self.assert_success(r) |
2167 | data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + payload['name'])).json() | |
e2367534 CH |
2168 | for k in payload.keys(): |
2169 | self.assertEquals(data[k], payload[k]) | |
37bc3d01 | 2170 | |
37663c3b CH |
2171 | def test_zone_delete(self): |
2172 | payload, zone = self.create_zone(kind='Native') | |
2173 | name = payload['name'] | |
46d06a12 | 2174 | r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name)) |
37663c3b CH |
2175 | self.assertEquals(r.status_code, 204) |
2176 | self.assertNotIn('Content-Type', r.headers) | |
2177 | ||
c1374bdb | 2178 | def test_search_rr_exact_zone(self): |
1d6b70f9 | 2179 | name = unique_zone_name() |
37bc3d01 | 2180 | self.create_zone(name=name, kind='Native') |
46d06a12 | 2181 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name)) |
c1374bdb | 2182 | self.assert_success_json(r) |
541bb91b | 2183 | print(r.json()) |
37bc3d01 CH |
2184 | self.assertEquals(r.json(), [{u'type': u'zone', u'name': name, u'zone_id': name}]) |
2185 | ||
c1374bdb | 2186 | def test_search_rr_substring(self): |
1d6b70f9 | 2187 | name = 'search-rr-zone.name.' |
37bc3d01 | 2188 | self.create_zone(name=name, kind='Native') |
46d06a12 | 2189 | r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=rr-zone")) |
c1374bdb | 2190 | self.assert_success_json(r) |
541bb91b | 2191 | print(r.json()) |
37bc3d01 CH |
2192 | # should return zone, SOA |
2193 | self.assertEquals(len(r.json()), 2) | |
ccfabd0d | 2194 | |
ccfabd0d CH |
2195 | @unittest.skipIf(not is_auth(), "Not applicable") |
2196 | class AuthZoneKeys(ApiTestCase, AuthZonesHelperMixin): | |
2197 | ||
2198 | def test_get_keys(self): | |
2199 | r = self.session.get( | |
2200 | self.url("/api/v1/servers/localhost/zones/powerdnssec.org./cryptokeys")) | |
2201 | self.assert_success_json(r) | |
2202 | keys = r.json() | |
2203 | self.assertGreater(len(keys), 0) | |
2204 | ||
2205 | key0 = deepcopy(keys[0]) | |
2206 | del key0['dnskey'] | |
b6bd795c | 2207 | del key0['ds'] |
ccfabd0d | 2208 | expected = { |
5d9c6182 PL |
2209 | u'algorithm': u'ECDSAP256SHA256', |
2210 | u'bits': 256, | |
ccfabd0d CH |
2211 | u'active': True, |
2212 | u'type': u'Cryptokey', | |
b6bd795c PL |
2213 | u'keytype': u'csk', |
2214 | u'flags': 257, | |
33918299 | 2215 | u'published': True, |
ccfabd0d CH |
2216 | u'id': 1} |
2217 | self.assertEquals(key0, expected) | |
2218 | ||
2219 | keydata = keys[0]['dnskey'].split() | |
2220 | self.assertEqual(len(keydata), 4) |