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