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