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