]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.api/test_Zones.py
serialtweaker: remove fallback, make DEFAULT an explicit choice
[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))
77bfe8de
PL
503 self.assertEquals(r.status_code, 404)
504 # Note: errors should probably contain json (see #5988)
505 # self.assertIn('Could not find domain ', r.json()['error'])
d38e81e6 506
4bdff352
CH
507 def test_create_slave_zone(self):
508 # Test that nameservers can be absent for slave zones.
6754ef71 509 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
4bdff352
CH
510 for k in ('name', 'masters', 'kind'):
511 self.assertIn(k, data)
512 self.assertEquals(data[k], payload[k])
4de11a54
CH
513 print "payload:", payload
514 print "data:", data
515 # Because slave zones don't get a SOA, we need to test that they'll show up in the zone list.
46d06a12 516 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
4de11a54
CH
517 zonelist = r.json()
518 print "zonelist:", zonelist
519 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
520 # Also test that fetching the zone works.
46d06a12 521 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54
CH
522 data = r.json()
523 print "zone (fetched):", data
524 for k in ('name', 'masters', 'kind'):
525 self.assertIn(k, data)
526 self.assertEquals(data[k], payload[k])
527 self.assertEqual(data['serial'], 0)
6754ef71 528 self.assertEqual(data['rrsets'], [])
4de11a54
CH
529
530 def test_delete_slave_zone(self):
6754ef71 531 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
46d06a12 532 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54 533 r.raise_for_status()
4bdff352 534
a426cb89 535 def test_retrieve_slave_zone(self):
6754ef71 536 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
a426cb89
CH
537 print "payload:", payload
538 print "data:", data
46d06a12 539 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/axfr-retrieve"))
a426cb89
CH
540 data = r.json()
541 print "status for axfr-retrieve:", data
542 self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
543 '\' from master 127.0.0.2')
544
545 def test_notify_master_zone(self):
6754ef71 546 name, payload, data = self.create_zone(kind='Master')
a426cb89
CH
547 print "payload:", payload
548 print "data:", data
46d06a12 549 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/notify"))
a426cb89
CH
550 data = r.json()
551 print "status for notify:", data
552 self.assertEqual(data['result'], 'Notification queued')
553
c1374bdb 554 def test_get_zone_with_symbols(self):
6754ef71 555 name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
3c3c006b 556 name = payload['name']
1d6b70f9 557 zone_id = (name.replace('/', '=2F'))
46d06a12 558 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id))
c1374bdb 559 data = r.json()
6bb25159 560 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'):
3c3c006b
CH
561 self.assertIn(k, data)
562 if k in payload:
563 self.assertEquals(data[k], payload[k])
564
c1374bdb 565 def test_get_zone(self):
46d06a12 566 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
05776d2f 567 domains = r.json()
1d6b70f9 568 example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
46d06a12 569 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id']))
c1374bdb 570 self.assert_success_json(r)
05776d2f
CH
571 data = r.json()
572 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
573 self.assertIn(k, data)
1d6b70f9 574 self.assertEquals(data['name'], 'example.com.')
7c0ba3d2 575
0f0e73fe
MS
576 def test_import_zone_broken(self):
577 payload = {}
578 payload['zone'] = """
579;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
580flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
581;; WARNING: recursion requested but not available
582
583;; OPT PSEUDOSECTION:
584; EDNS: version: 0, flags:; udp: 1680
585;; QUESTION SECTION:
586;powerdns.com. IN SOA
587
588;; ANSWER SECTION:
589powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
590powerdns-broken.com. 3600 IN NS powerdnssec2.ds9a.nl.
591powerdns-broken.com. 3600 IN AAAA 2001:888:2000:1d::2
592powerdns-broken.com. 86400 IN A 82.94.213.34
593powerdns-broken.com. 3600 IN MX 0 xs.powerdns.com.
594powerdns-broken.com. 3600 IN NS powerdnssec1.ds9a.nl.
595powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
596"""
1d6b70f9 597 payload['name'] = 'powerdns-broken.com.'
0f0e73fe
MS
598 payload['kind'] = 'Master'
599 payload['nameservers'] = []
600 r = self.session.post(
46d06a12 601 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
602 data=json.dumps(payload),
603 headers={'content-type': 'application/json'})
604 self.assertEquals(r.status_code, 422)
605
1d6b70f9
CH
606 def test_import_zone_axfr_outofzone(self):
607 # Ensure we don't create out-of-zone records
608 name = unique_zone_name()
609 payload = {}
610 payload['zone'] = """
611NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
612NAME 3600 IN NS powerdnssec2.ds9a.nl.
613example.org. 3600 IN AAAA 2001:888:2000:1d::2
614NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
615""".replace('NAME', name)
616 payload['name'] = name
617 payload['kind'] = 'Master'
618 payload['nameservers'] = []
619 r = self.session.post(
620 self.url("/api/v1/servers/localhost/zones"),
621 data=json.dumps(payload),
622 headers={'content-type': 'application/json'})
623 self.assertEquals(r.status_code, 422)
624 self.assertEqual(r.json()['error'], 'RRset example.org. IN AAAA: Name is out of zone')
625
0f0e73fe
MS
626 def test_import_zone_axfr(self):
627 payload = {}
628 payload['zone'] = """
629;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
630;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
631;; WARNING: recursion requested but not available
632
633;; OPT PSEUDOSECTION:
634; EDNS: version: 0, flags:; udp: 1680
635;; QUESTION SECTION:
636;powerdns.com. IN SOA
637
638;; ANSWER SECTION:
639powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
640powerdns.com. 3600 IN NS powerdnssec2.ds9a.nl.
641powerdns.com. 3600 IN AAAA 2001:888:2000:1d::2
642powerdns.com. 86400 IN A 82.94.213.34
643powerdns.com. 3600 IN MX 0 xs.powerdns.com.
644powerdns.com. 3600 IN NS powerdnssec1.ds9a.nl.
645powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
646"""
1d6b70f9 647 payload['name'] = 'powerdns.com.'
0f0e73fe
MS
648 payload['kind'] = 'Master'
649 payload['nameservers'] = []
1d6b70f9 650 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 651 r = self.session.post(
46d06a12 652 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
653 data=json.dumps(payload),
654 headers={'content-type': 'application/json'})
655 self.assert_success_json(r)
656 data = r.json()
657 self.assertIn('name', data)
0f0e73fe 658
90568eb2
MS
659 expected = {
660 'NS': [
6754ef71
CH
661 {'content': 'powerdnssec1.ds9a.nl.'},
662 {'content': 'powerdnssec2.ds9a.nl.'},
663 ],
90568eb2 664 'SOA': [
6754ef71
CH
665 {'content': 'powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800'},
666 ],
90568eb2 667 'MX': [
6754ef71
CH
668 {'content': '0 xs.powerdns.com.'},
669 ],
90568eb2 670 'A': [
6754ef71
CH
671 {'content': '82.94.213.34', 'name': 'powerdns.com.'},
672 ],
90568eb2 673 'AAAA': [
6754ef71
CH
674 {'content': '2001:888:2000:1d::2', 'name': 'powerdns.com.'},
675 ],
90568eb2 676 }
0f0e73fe 677
6754ef71 678 eq_zone_rrsets(data['rrsets'], expected)
1d6b70f9 679
e3675a8a 680 # check content in DB is stored WITHOUT trailing dot.
1d6b70f9 681 dbrecs = get_db_records(payload['name'], 'NS')
e3675a8a
CH
682 dbrec = next((dbrec for dbrec in dbrecs if dbrec['content'].startswith('powerdnssec1')))
683 self.assertEqual(dbrec['content'], 'powerdnssec1.ds9a.nl')
0f0e73fe
MS
684
685 def test_import_zone_bind(self):
686 payload = {}
687 payload['zone'] = """
688$TTL 86400 ; 24 hours could have been written as 24h or 1d
689; $TTL used for all RRs without explicit TTL value
690$ORIGIN example.org.
691@ 1D IN SOA ns1.example.org. hostmaster.example.org. (
692 2002022401 ; serial
693 3H ; refresh
694 15 ; retry
695 1w ; expire
696 3h ; minimum
697 )
698 IN NS ns1.example.org. ; in the domain
699 IN NS ns2.smokeyjoe.com. ; external to domain
700 IN MX 10 mail.another.com. ; external mail provider
701; server host definitions
1d6b70f9 702ns1 IN A 192.168.0.1 ;name server definition
0f0e73fe
MS
703www IN A 192.168.0.2 ;web server definition
704ftp IN CNAME www.example.org. ;ftp server definition
705; non server domain hosts
706bill IN A 192.168.0.3
1d6b70f9 707fred IN A 192.168.0.4
0f0e73fe 708"""
1d6b70f9 709 payload['name'] = 'example.org.'
0f0e73fe
MS
710 payload['kind'] = 'Master'
711 payload['nameservers'] = []
1d6b70f9 712 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 713 r = self.session.post(
46d06a12 714 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
715 data=json.dumps(payload),
716 headers={'content-type': 'application/json'})
717 self.assert_success_json(r)
718 data = r.json()
719 self.assertIn('name', data)
0f0e73fe 720
90568eb2
MS
721 expected = {
722 'NS': [
6754ef71
CH
723 {'content': 'ns1.example.org.'},
724 {'content': 'ns2.smokeyjoe.com.'},
725 ],
90568eb2 726 'SOA': [
6754ef71
CH
727 {'content': 'ns1.example.org. hostmaster.example.org. 2002022401 10800 15 604800 10800'},
728 ],
90568eb2 729 'MX': [
6754ef71
CH
730 {'content': '10 mail.another.com.'},
731 ],
90568eb2 732 'A': [
6754ef71
CH
733 {'content': '192.168.0.1', 'name': 'ns1.example.org.'},
734 {'content': '192.168.0.2', 'name': 'www.example.org.'},
735 {'content': '192.168.0.3', 'name': 'bill.example.org.'},
736 {'content': '192.168.0.4', 'name': 'fred.example.org.'},
737 ],
90568eb2 738 'CNAME': [
6754ef71
CH
739 {'content': 'www.example.org.', 'name': 'ftp.example.org.'},
740 ],
90568eb2 741 }
0f0e73fe 742
6754ef71 743 eq_zone_rrsets(data['rrsets'], expected)
0f0e73fe 744
c1374bdb 745 def test_export_zone_json(self):
6754ef71 746 name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
747 # export it
748 r = self.session.get(
46d06a12 749 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
750 headers={'accept': 'application/json;q=0.9,*/*;q=0.8'}
751 )
c1374bdb 752 self.assert_success_json(r)
a83004d3
CH
753 data = r.json()
754 self.assertIn('zone', data)
1d6b70f9
CH
755 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
756 name + '\t3600\tNS\tns2.foo.com.',
757 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
758 ' 0 10800 3600 604800 3600']
a83004d3
CH
759 self.assertEquals(data['zone'].strip().split('\n'), expected_data)
760
c1374bdb 761 def test_export_zone_text(self):
6754ef71 762 name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
763 # export it
764 r = self.session.get(
46d06a12 765 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
766 headers={'accept': '*/*'}
767 )
768 data = r.text.strip().split("\n")
1d6b70f9
CH
769 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
770 name + '\t3600\tNS\tns2.foo.com.',
771 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
772 ' 0 10800 3600 604800 3600']
a83004d3
CH
773 self.assertEquals(data, expected_data)
774
c1374bdb 775 def test_update_zone(self):
6754ef71 776 name, payload, zone = self.create_zone()
bee2acae 777 name = payload['name']
d29d5db7 778 # update, set as Master and enable SOA-EDIT-API
7c0ba3d2
CH
779 payload = {
780 'kind': 'Master',
c1374bdb 781 'masters': ['192.0.2.1', '192.0.2.2'],
6bb25159
MS
782 'soa_edit_api': 'EPOCH',
783 'soa_edit': 'EPOCH'
7c0ba3d2
CH
784 }
785 r = self.session.put(
46d06a12 786 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
787 data=json.dumps(payload),
788 headers={'content-type': 'application/json'})
f0e76cee
CH
789 self.assert_success(r)
790 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
7c0ba3d2
CH
791 for k in payload.keys():
792 self.assertIn(k, data)
793 self.assertEquals(data[k], payload[k])
d29d5db7 794 # update, back to Native and empty(off)
7c0ba3d2 795 payload = {
d29d5db7 796 'kind': 'Native',
6bb25159
MS
797 'soa_edit_api': '',
798 'soa_edit': ''
7c0ba3d2
CH
799 }
800 r = self.session.put(
46d06a12 801 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
802 data=json.dumps(payload),
803 headers={'content-type': 'application/json'})
f0e76cee
CH
804 self.assert_success(r)
805 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
7c0ba3d2
CH
806 for k in payload.keys():
807 self.assertIn(k, data)
808 self.assertEquals(data[k], payload[k])
b3905a3d 809
c1374bdb 810 def test_zone_rr_update(self):
6754ef71 811 name, payload, zone = self.create_zone()
b3905a3d 812 # do a replace (= update)
d708640f 813 rrset = {
b3905a3d
CH
814 'changetype': 'replace',
815 'name': name,
8ce0dc75 816 'type': 'ns',
6754ef71 817 'ttl': 3600,
b3905a3d
CH
818 'records': [
819 {
1d6b70f9 820 "content": "ns1.bar.com.",
cea26350
CH
821 "disabled": False
822 },
823 {
1d6b70f9 824 "content": "ns2-disabled.bar.com.",
cea26350 825 "disabled": True
b3905a3d
CH
826 }
827 ]
828 }
d708640f 829 payload = {'rrsets': [rrset]}
b3905a3d 830 r = self.session.patch(
46d06a12 831 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
832 data=json.dumps(payload),
833 headers={'content-type': 'application/json'})
f0e76cee 834 self.assert_success(r)
b3905a3d 835 # verify that (only) the new record is there
f0e76cee 836 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 837 self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset['records'])
b3905a3d 838
c1374bdb 839 def test_zone_rr_update_mx(self):
05cf6a71 840 # Important to test with MX records, as they have a priority field, which must end up in the content field.
6754ef71 841 name, payload, zone = self.create_zone()
41e3b10e 842 # do a replace (= update)
d708640f 843 rrset = {
41e3b10e
CH
844 'changetype': 'replace',
845 'name': name,
846 'type': 'MX',
6754ef71 847 'ttl': 3600,
41e3b10e
CH
848 'records': [
849 {
1d6b70f9 850 "content": "10 mail.example.org.",
41e3b10e
CH
851 "disabled": False
852 }
853 ]
854 }
d708640f 855 payload = {'rrsets': [rrset]}
41e3b10e 856 r = self.session.patch(
46d06a12 857 self.url("/api/v1/servers/localhost/zones/" + name),
41e3b10e
CH
858 data=json.dumps(payload),
859 headers={'content-type': 'application/json'})
f0e76cee 860 self.assert_success(r)
41e3b10e 861 # verify that (only) the new record is there
f0e76cee 862 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 863 self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset['records'])
d708640f 864
c1374bdb 865 def test_zone_rr_update_multiple_rrsets(self):
6754ef71 866 name, payload, zone = self.create_zone()
d708640f
CH
867 rrset1 = {
868 'changetype': 'replace',
869 'name': name,
870 'type': 'NS',
6754ef71 871 'ttl': 3600,
d708640f
CH
872 'records': [
873 {
6754ef71 874
1d6b70f9 875 "content": "ns9999.example.com.",
d708640f
CH
876 "disabled": False
877 }
878 ]
879 }
880 rrset2 = {
881 'changetype': 'replace',
882 'name': name,
883 'type': 'MX',
6754ef71 884 'ttl': 3600,
d708640f
CH
885 'records': [
886 {
1d6b70f9 887 "content": "10 mx444.example.com.",
d708640f
CH
888 "disabled": False
889 }
890 ]
891 }
892 payload = {'rrsets': [rrset1, rrset2]}
893 r = self.session.patch(
46d06a12 894 self.url("/api/v1/servers/localhost/zones/" + name),
d708640f
CH
895 data=json.dumps(payload),
896 headers={'content-type': 'application/json'})
f0e76cee 897 self.assert_success(r)
d708640f 898 # verify that all rrsets have been updated
f0e76cee 899 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71
CH
900 self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset1['records'])
901 self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset2['records'])
41e3b10e 902
e3675a8a
CH
903 def test_zone_rr_update_duplicate_record(self):
904 name, payload, zone = self.create_zone()
905 rrset = {
906 'changetype': 'replace',
907 'name': name,
908 'type': 'NS',
909 'ttl': 3600,
910 'records': [
911 {"content": "ns9999.example.com.", "disabled": False},
912 {"content": "ns9996.example.com.", "disabled": False},
913 {"content": "ns9987.example.com.", "disabled": False},
914 {"content": "ns9988.example.com.", "disabled": False},
915 {"content": "ns9999.example.com.", "disabled": False},
916 ]
917 }
918 payload = {'rrsets': [rrset]}
919 r = self.session.patch(
920 self.url("/api/v1/servers/localhost/zones/" + name),
921 data=json.dumps(payload),
922 headers={'content-type': 'application/json'})
923 self.assertEquals(r.status_code, 422)
924 self.assertIn('Duplicate record in RRset', r.json()['error'])
925
c1374bdb 926 def test_zone_rr_delete(self):
6754ef71 927 name, payload, zone = self.create_zone()
b3905a3d 928 # do a delete of all NS records (these are created with the zone)
d708640f 929 rrset = {
b3905a3d
CH
930 'changetype': 'delete',
931 'name': name,
932 'type': 'NS'
933 }
d708640f 934 payload = {'rrsets': [rrset]}
b3905a3d 935 r = self.session.patch(
46d06a12 936 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
937 data=json.dumps(payload),
938 headers={'content-type': 'application/json'})
f0e76cee 939 self.assert_success(r)
b3905a3d 940 # verify that the records are gone
f0e76cee 941 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 942 self.assertIsNone(get_rrset(data, name, 'NS'))
cea26350 943
c1374bdb 944 def test_zone_disable_reenable(self):
d29d5db7 945 # This also tests that SOA-EDIT-API works.
6754ef71 946 name, payload, zone = self.create_zone(soa_edit_api='EPOCH')
cea26350 947 # disable zone by disabling SOA
d708640f 948 rrset = {
cea26350
CH
949 'changetype': 'replace',
950 'name': name,
951 'type': 'SOA',
6754ef71 952 'ttl': 3600,
cea26350
CH
953 'records': [
954 {
1d6b70f9 955 "content": "ns1.bar.com. hostmaster.foo.org. 1 1 1 1 1",
cea26350
CH
956 "disabled": True
957 }
958 ]
959 }
d708640f 960 payload = {'rrsets': [rrset]}
cea26350 961 r = self.session.patch(
46d06a12 962 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
963 data=json.dumps(payload),
964 headers={'content-type': 'application/json'})
f0e76cee 965 self.assert_success(r)
d29d5db7 966 # check SOA serial has been edited
f0e76cee
CH
967 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
968 soa_serial1 = get_first_rec(data, name, 'SOA')['content'].split()[2]
d29d5db7
CH
969 self.assertNotEquals(soa_serial1, '1')
970 # make sure domain is still in zone list (disabled SOA!)
46d06a12 971 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
cea26350
CH
972 domains = r.json()
973 self.assertEquals(len([domain for domain in domains if domain['name'] == name]), 1)
d29d5db7
CH
974 # sleep 1sec to ensure the EPOCH value changes for the next request
975 time.sleep(1)
cea26350 976 # verify that modifying it still works
d708640f
CH
977 rrset['records'][0]['disabled'] = False
978 payload = {'rrsets': [rrset]}
cea26350 979 r = self.session.patch(
46d06a12 980 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
981 data=json.dumps(payload),
982 headers={'content-type': 'application/json'})
f0e76cee 983 self.assert_success(r)
d29d5db7 984 # check SOA serial has been edited again
f0e76cee
CH
985 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
986 soa_serial2 = get_first_rec(data, name, 'SOA')['content'].split()[2]
d29d5db7
CH
987 self.assertNotEquals(soa_serial2, '1')
988 self.assertNotEquals(soa_serial2, soa_serial1)
02945d9a 989
c1374bdb 990 def test_zone_rr_update_out_of_zone(self):
6754ef71 991 name, payload, zone = self.create_zone()
35f26cc5 992 # replace with qname mismatch
d708640f 993 rrset = {
35f26cc5 994 'changetype': 'replace',
1d6b70f9 995 'name': 'not-in-zone.',
35f26cc5 996 'type': 'NS',
6754ef71 997 'ttl': 3600,
35f26cc5
CH
998 'records': [
999 {
1d6b70f9 1000 "content": "ns1.bar.com.",
35f26cc5
CH
1001 "disabled": False
1002 }
1003 ]
1004 }
d708640f 1005 payload = {'rrsets': [rrset]}
35f26cc5 1006 r = self.session.patch(
46d06a12 1007 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
1008 data=json.dumps(payload),
1009 headers={'content-type': 'application/json'})
1010 self.assertEquals(r.status_code, 422)
1011 self.assertIn('out of zone', r.json()['error'])
1012
1d6b70f9 1013 def test_zone_rr_update_restricted_chars(self):
6754ef71 1014 name, payload, zone = self.create_zone()
1d6b70f9
CH
1015 # replace with qname mismatch
1016 rrset = {
1017 'changetype': 'replace',
1018 'name': 'test:' + name,
1019 'type': 'NS',
6754ef71 1020 'ttl': 3600,
1d6b70f9
CH
1021 'records': [
1022 {
1d6b70f9
CH
1023 "content": "ns1.bar.com.",
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('contains unsupported characters', r.json()['error'])
1035
24cd86ca 1036 def test_rrset_unknown_type(self):
6754ef71 1037 name, payload, zone = self.create_zone()
24cd86ca
CH
1038 rrset = {
1039 'changetype': 'replace',
1040 'name': name,
1041 'type': 'FAFAFA',
6754ef71 1042 'ttl': 3600,
24cd86ca
CH
1043 'records': [
1044 {
24cd86ca
CH
1045 "content": "4.3.2.1",
1046 "disabled": False
1047 }
1048 ]
1049 }
1050 payload = {'rrsets': [rrset]}
46d06a12 1051 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
24cd86ca
CH
1052 headers={'content-type': 'application/json'})
1053 self.assertEquals(r.status_code, 422)
1054 self.assertIn('unknown type', r.json()['error'])
1055
8560f36a
CH
1056 def test_rrset_cname_and_other(self):
1057 name, payload, zone = self.create_zone()
1058 rrset = {
1059 'changetype': 'replace',
1060 'name': name,
1061 'type': 'CNAME',
1062 'ttl': 3600,
1063 'records': [
1064 {
1065 "content": "example.org.",
1066 "disabled": False
1067 }
1068 ]
1069 }
1070 payload = {'rrsets': [rrset]}
1071 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1072 headers={'content-type': 'application/json'})
1073 self.assertEquals(r.status_code, 422)
1074 self.assertIn('Conflicts with pre-existing non-CNAME RRset', r.json()['error'])
1075
1076 def test_rrset_other_and_cname(self):
1077 name, payload, zone = self.create_zone()
1078 rrset = {
1079 'changetype': 'replace',
1080 'name': 'sub.'+name,
1081 'type': 'CNAME',
1082 'ttl': 3600,
1083 'records': [
1084 {
1085 "content": "example.org.",
1086 "disabled": False
1087 }
1088 ]
1089 }
1090 payload = {'rrsets': [rrset]}
1091 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1092 headers={'content-type': 'application/json'})
1093 self.assert_success(r)
1094 rrset = {
1095 'changetype': 'replace',
1096 'name': 'sub.'+name,
1097 'type': 'A',
1098 'ttl': 3600,
1099 'records': [
1100 {
1101 "content": "1.2.3.4",
1102 "disabled": False
1103 }
1104 ]
1105 }
1106 payload = {'rrsets': [rrset]}
1107 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1108 headers={'content-type': 'application/json'})
1109 self.assertEquals(r.status_code, 422)
1110 self.assertIn('Conflicts with pre-existing CNAME RRset', r.json()['error'])
1111
1e5b9ab9
CH
1112 def test_create_zone_with_leading_space(self):
1113 # Actual regression.
6754ef71 1114 name, payload, zone = self.create_zone()
1e5b9ab9
CH
1115 rrset = {
1116 'changetype': 'replace',
1117 'name': name,
1118 'type': 'A',
6754ef71 1119 'ttl': 3600,
1e5b9ab9
CH
1120 'records': [
1121 {
1e5b9ab9
CH
1122 "content": " 4.3.2.1",
1123 "disabled": False
1124 }
1125 ]
1126 }
1127 payload = {'rrsets': [rrset]}
46d06a12 1128 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1e5b9ab9
CH
1129 headers={'content-type': 'application/json'})
1130 self.assertEquals(r.status_code, 422)
1131 self.assertIn('Not in expected format', r.json()['error'])
1132
c1374bdb 1133 def test_zone_rr_delete_out_of_zone(self):
6754ef71 1134 name, payload, zone = self.create_zone()
d708640f 1135 rrset = {
35f26cc5 1136 'changetype': 'delete',
1d6b70f9 1137 'name': 'not-in-zone.',
35f26cc5
CH
1138 'type': 'NS'
1139 }
d708640f 1140 payload = {'rrsets': [rrset]}
35f26cc5 1141 r = self.session.patch(
46d06a12 1142 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
1143 data=json.dumps(payload),
1144 headers={'content-type': 'application/json'})
34df6ecc 1145 print r.content
f0e76cee 1146 self.assert_success(r) # succeed so users can fix their wrong, old data
35f26cc5 1147
37663c3b 1148 def test_zone_delete(self):
6754ef71 1149 name, payload, zone = self.create_zone()
46d06a12 1150 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
1151 self.assertEquals(r.status_code, 204)
1152 self.assertNotIn('Content-Type', r.headers)
1153
c1374bdb 1154 def test_zone_comment_create(self):
6754ef71 1155 name, payload, zone = self.create_zone()
d708640f 1156 rrset = {
6cc98ddf
CH
1157 'changetype': 'replace',
1158 'name': name,
1159 'type': 'NS',
6754ef71 1160 'ttl': 3600,
6cc98ddf
CH
1161 'comments': [
1162 {
1163 'account': 'test1',
1164 'content': 'blah blah',
1165 },
1166 {
1167 'account': 'test2',
1168 'content': 'blah blah bleh',
1169 }
1170 ]
1171 }
d708640f 1172 payload = {'rrsets': [rrset]}
6cc98ddf 1173 r = self.session.patch(
46d06a12 1174 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1175 data=json.dumps(payload),
1176 headers={'content-type': 'application/json'})
f0e76cee 1177 self.assert_success(r)
6cc98ddf
CH
1178 # make sure the comments have been set, and that the NS
1179 # records are still present
f0e76cee
CH
1180 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1181 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1182 print serverset
1183 self.assertNotEquals(serverset['records'], [])
1184 self.assertNotEquals(serverset['comments'], [])
6cc98ddf 1185 # verify that modified_at has been set by pdns
6754ef71 1186 self.assertNotEquals([c for c in serverset['comments']][0]['modified_at'], 0)
0d7f3c75
CH
1187 # verify that TTL is correct (regression test)
1188 self.assertEquals(serverset['ttl'], 3600)
6cc98ddf 1189
c1374bdb 1190 def test_zone_comment_delete(self):
6cc98ddf 1191 # Test: Delete ONLY comments.
6754ef71 1192 name, payload, zone = self.create_zone()
d708640f 1193 rrset = {
6cc98ddf
CH
1194 'changetype': 'replace',
1195 'name': name,
1196 'type': 'NS',
1197 'comments': []
1198 }
d708640f 1199 payload = {'rrsets': [rrset]}
6cc98ddf 1200 r = self.session.patch(
46d06a12 1201 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1202 data=json.dumps(payload),
1203 headers={'content-type': 'application/json'})
f0e76cee 1204 self.assert_success(r)
6cc98ddf 1205 # make sure the NS records are still present
f0e76cee
CH
1206 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1207 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1208 print serverset
1209 self.assertNotEquals(serverset['records'], [])
1210 self.assertEquals(serverset['comments'], [])
6cc98ddf 1211
c1374bdb 1212 def test_zone_comment_stay_intact(self):
6cc98ddf 1213 # Test if comments on an rrset stay intact if the rrset is replaced
6754ef71 1214 name, payload, zone = self.create_zone()
6cc98ddf 1215 # create a comment
d708640f 1216 rrset = {
6cc98ddf
CH
1217 'changetype': 'replace',
1218 'name': name,
1219 'type': 'NS',
1220 'comments': [
1221 {
1222 'account': 'test1',
1223 'content': 'oh hi there',
2696eea0 1224 'modified_at': 1111
6cc98ddf
CH
1225 }
1226 ]
1227 }
d708640f 1228 payload = {'rrsets': [rrset]}
6cc98ddf 1229 r = self.session.patch(
46d06a12 1230 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1231 data=json.dumps(payload),
1232 headers={'content-type': 'application/json'})
f0e76cee 1233 self.assert_success(r)
6cc98ddf 1234 # replace rrset records
d708640f 1235 rrset2 = {
6cc98ddf
CH
1236 'changetype': 'replace',
1237 'name': name,
1238 'type': 'NS',
6754ef71 1239 'ttl': 3600,
6cc98ddf
CH
1240 'records': [
1241 {
1d6b70f9 1242 "content": "ns1.bar.com.",
6cc98ddf
CH
1243 "disabled": False
1244 }
1245 ]
1246 }
d708640f 1247 payload2 = {'rrsets': [rrset2]}
6cc98ddf 1248 r = self.session.patch(
46d06a12 1249 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1250 data=json.dumps(payload2),
1251 headers={'content-type': 'application/json'})
f0e76cee 1252 self.assert_success(r)
6cc98ddf 1253 # make sure the comments still exist
f0e76cee
CH
1254 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1255 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1256 print serverset
1257 self.assertEquals(serverset['records'], rrset2['records'])
1258 self.assertEquals(serverset['comments'], rrset['comments'])
6cc98ddf 1259
3fe7c7d6
CH
1260 def test_zone_auto_ptr_ipv4_create(self):
1261 revzone = '4.2.192.in-addr.arpa.'
1262 _, _, revzonedata = self.create_zone(name=revzone)
1263 name = unique_zone_name()
1264 rrset = {
1265 "name": name,
1266 "type": "A",
1267 "ttl": 3600,
1268 "records": [{
1269 "content": "192.2.4.44",
1270 "disabled": False,
1271 "set-ptr": True,
1272 }],
1273 }
1274 name, payload, data = self.create_zone(name=name, rrsets=[rrset])
1275 del rrset['records'][0]['set-ptr']
1276 self.assertEquals(get_rrset(data, name, 'A')['records'], rrset['records'])
1277 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1278 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
1279 print revsets
1280 self.assertEquals(revsets, [{
1281 u'name': u'44.4.2.192.in-addr.arpa.',
1282 u'ttl': 3600,
1283 u'type': u'PTR',
1284 u'comments': [],
1285 u'records': [{
1286 u'content': name,
1287 u'disabled': False,
1288 }],
1289 }])
1290 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1291 self.assertGreater(r['serial'], revzonedata['serial'])
1292
1293 def test_zone_auto_ptr_ipv4_update(self):
1d6b70f9 1294 revzone = '0.2.192.in-addr.arpa.'
a41c038a 1295 _, _, revzonedata = self.create_zone(name=revzone)
6754ef71 1296 name, payload, zone = self.create_zone()
d708640f 1297 rrset = {
d1587ceb
CH
1298 'changetype': 'replace',
1299 'name': name,
1300 'type': 'A',
6754ef71 1301 'ttl': 3600,
d1587ceb
CH
1302 'records': [
1303 {
d1587ceb
CH
1304 "content": '192.2.0.2',
1305 "disabled": False,
1306 "set-ptr": True
1307 }
1308 ]
1309 }
d708640f 1310 payload = {'rrsets': [rrset]}
d1587ceb 1311 r = self.session.patch(
46d06a12 1312 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1313 data=json.dumps(payload),
1314 headers={'content-type': 'application/json'})
f0e76cee 1315 self.assert_success(r)
a41c038a
CH
1316 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1317 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
6754ef71
CH
1318 print revsets
1319 self.assertEquals(revsets, [{
1320 u'name': u'2.0.2.192.in-addr.arpa.',
d1587ceb 1321 u'ttl': 3600,
d1587ceb 1322 u'type': u'PTR',
6754ef71
CH
1323 u'comments': [],
1324 u'records': [{
1325 u'content': name,
1326 u'disabled': False,
1327 }],
d1587ceb 1328 }])
a41c038a
CH
1329 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1330 self.assertGreater(r['serial'], revzonedata['serial'])
d1587ceb 1331
3fe7c7d6 1332 def test_zone_auto_ptr_ipv6_update(self):
d1587ceb 1333 # 2001:DB8::bb:aa
1d6b70f9 1334 revzone = '8.b.d.0.1.0.0.2.ip6.arpa.'
a41c038a 1335 _, _, revzonedata = self.create_zone(name=revzone)
6754ef71 1336 name, payload, zone = self.create_zone()
d708640f 1337 rrset = {
d1587ceb
CH
1338 'changetype': 'replace',
1339 'name': name,
1340 'type': 'AAAA',
6754ef71 1341 'ttl': 3600,
d1587ceb
CH
1342 'records': [
1343 {
d1587ceb
CH
1344 "content": '2001:DB8::bb:aa',
1345 "disabled": False,
1346 "set-ptr": True
1347 }
1348 ]
1349 }
d708640f 1350 payload = {'rrsets': [rrset]}
d1587ceb 1351 r = self.session.patch(
46d06a12 1352 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1353 data=json.dumps(payload),
1354 headers={'content-type': 'application/json'})
f0e76cee 1355 self.assert_success(r)
a41c038a
CH
1356 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1357 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
6754ef71
CH
1358 print revsets
1359 self.assertEquals(revsets, [{
1360 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 1361 u'ttl': 3600,
d1587ceb 1362 u'type': u'PTR',
6754ef71
CH
1363 u'comments': [],
1364 u'records': [{
1365 u'content': name,
1366 u'disabled': False,
1367 }],
d1587ceb 1368 }])
a41c038a
CH
1369 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1370 self.assertGreater(r['serial'], revzonedata['serial'])
d1587ceb 1371
c1374bdb 1372 def test_search_rr_exact_zone(self):
b1902fab 1373 name = unique_zone_name()
1d6b70f9
CH
1374 self.create_zone(name=name, serial=22, soa_edit_api='')
1375 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.')))
c1374bdb 1376 self.assert_success_json(r)
b1902fab 1377 print r.json()
1d6b70f9
CH
1378 self.assertEquals(r.json(), [
1379 {u'object_type': u'zone', u'name': name, u'zone_id': name},
1380 {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600',
1381 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1382 u'ttl': 3600, u'type': u'SOA', u'name': name},
1383 {u'content': u'ns1.example.com.',
1384 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1385 u'ttl': 3600, u'type': u'NS', u'name': name},
1386 {u'content': u'ns2.example.com.',
1387 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1388 u'ttl': 3600, u'type': u'NS', u'name': name},
1389 ])
b1902fab 1390
c1374bdb 1391 def test_search_rr_substring(self):
1d6b70f9 1392 name = 'search-rr-zone.name.'
b1902fab 1393 self.create_zone(name=name)
46d06a12 1394 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-zone*"))
c1374bdb 1395 self.assert_success_json(r)
b1902fab
CH
1396 print r.json()
1397 # should return zone, SOA, ns1, ns2
60a8e825 1398 self.assertEquals(len(r.json()), 4)
b1902fab 1399
c1374bdb 1400 def test_search_rr_case_insensitive(self):
1d6b70f9 1401 name = 'search-rr-insenszone.name.'
57cb86d8 1402 self.create_zone(name=name)
46d06a12 1403 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-insensZONE*"))
c1374bdb 1404 self.assert_success_json(r)
57cb86d8
CH
1405 print r.json()
1406 # should return zone, SOA, ns1, ns2
60a8e825 1407 self.assertEquals(len(r.json()), 4)
57cb86d8 1408
7cbc5255
CH
1409 def test_search_after_rectify_with_ent(self):
1410 name = 'search-rectified.name.'
1411 rrset = {
1412 "name": 'sub.sub.' + name,
1413 "type": "A",
1414 "ttl": 3600,
1415 "records": [{
1416 "content": "4.3.2.1",
1417 "disabled": False,
1418 }],
1419 }
1420 self.create_zone(name=name, rrsets=[rrset])
1421 pdnsutil_rectify(name)
1422 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*search-rectified*"))
1423 self.assert_success_json(r)
1424 print r.json()
1425 # should return zone, SOA, ns1, ns2, sub.sub A (but not the ENT)
1426 self.assertEquals(len(r.json()), 5)
1427
986e4858
PL
1428 def test_rrset_parameter_post_false(self):
1429 name = unique_zone_name()
1430 payload = {
1431 'name': name,
1432 'kind': 'Native',
1433 'nameservers': ['ns1.example.com.', 'ns2.example.com.']
1434 }
1435 r = self.session.post(
1436 self.url("/api/v1/servers/localhost/zones?rrsets=false"),
1437 data=json.dumps(payload),
1438 headers={'content-type': 'application/json'})
1439 print r.json()
1440 self.assert_success_json(r)
1441 self.assertEquals(r.status_code, 201)
1442 self.assertEquals(r.json().get('rrsets'), None)
1443
1444 def test_rrset_false_parameter(self):
1445 name = unique_zone_name()
1446 self.create_zone(name=name, kind='Native')
1447 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=false"))
1448 self.assert_success_json(r)
1449 print r.json()
1450 self.assertEquals(r.json().get('rrsets'), None)
1451
1452 def test_rrset_true_parameter(self):
1453 name = unique_zone_name()
1454 self.create_zone(name=name, kind='Native')
1455 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=true"))
1456 self.assert_success_json(r)
1457 print r.json()
1458 self.assertEquals(len(r.json().get('rrsets')), 2)
1459
1460 def test_wrong_rrset_parameter(self):
1461 name = unique_zone_name()
1462 self.create_zone(name=name, kind='Native')
1463 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=foobar"))
1464 self.assertEquals(r.status_code, 422)
1465 self.assertIn("'rrsets' request parameter value 'foobar' is not supported", r.json()['error'])
1466
02945d9a 1467
406497f5
CH
1468@unittest.skipIf(not is_auth(), "Not applicable")
1469class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
1470
1471 def setUp(self):
1472 super(AuthRootZone, self).setUp()
1473 # zone name is not unique, so delete the zone before each individual test.
46d06a12 1474 self.session.delete(self.url("/api/v1/servers/localhost/zones/=2E"))
406497f5
CH
1475
1476 def test_create_zone(self):
6754ef71 1477 name, payload, data = self.create_zone(name='.', serial=22, soa_edit_api='')
406497f5
CH
1478 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'):
1479 self.assertIn(k, data)
1480 if k in payload:
1481 self.assertEquals(data[k], payload[k])
406497f5 1482 # validate generated SOA
6754ef71 1483 rec = get_first_rec(data, '.', 'SOA')
406497f5 1484 self.assertEquals(
6754ef71 1485 rec['content'],
1d6b70f9 1486 "a.misconfigured.powerdns.server. hostmaster. " + str(payload['serial']) +
406497f5
CH
1487 " 10800 3600 604800 3600"
1488 )
1489 # Regression test: verify zone list works
46d06a12 1490 zonelist = self.session.get(self.url("/api/v1/servers/localhost/zones")).json()
406497f5
CH
1491 print "zonelist:", zonelist
1492 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
1493 # Also test that fetching the zone works.
1494 print "id:", data['id']
1495 self.assertEquals(data['id'], '=2E')
46d06a12 1496 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json()
406497f5
CH
1497 print "zone (fetched):", data
1498 for k in ('name', 'kind'):
1499 self.assertIn(k, data)
1500 self.assertEquals(data[k], payload[k])
6754ef71 1501 self.assertEqual(data['rrsets'][0]['name'], '.')
406497f5
CH
1502
1503 def test_update_zone(self):
6754ef71 1504 name, payload, zone = self.create_zone(name='.')
406497f5
CH
1505 zone_id = '=2E'
1506 # update, set as Master and enable SOA-EDIT-API
1507 payload = {
1508 'kind': 'Master',
1509 'masters': ['192.0.2.1', '192.0.2.2'],
1510 'soa_edit_api': 'EPOCH',
1511 'soa_edit': 'EPOCH'
1512 }
1513 r = self.session.put(
46d06a12 1514 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1515 data=json.dumps(payload),
1516 headers={'content-type': 'application/json'})
f0e76cee
CH
1517 self.assert_success(r)
1518 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
406497f5
CH
1519 for k in payload.keys():
1520 self.assertIn(k, data)
1521 self.assertEquals(data[k], payload[k])
1522 # update, back to Native and empty(off)
1523 payload = {
1524 'kind': 'Native',
1525 'soa_edit_api': '',
1526 'soa_edit': ''
1527 }
1528 r = self.session.put(
46d06a12 1529 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1530 data=json.dumps(payload),
1531 headers={'content-type': 'application/json'})
f0e76cee
CH
1532 self.assert_success(r)
1533 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
406497f5
CH
1534 for k in payload.keys():
1535 self.assertIn(k, data)
1536 self.assertEquals(data[k], payload[k])
1537
1538
c1374bdb 1539@unittest.skipIf(not is_recursor(), "Not applicable")
02945d9a
CH
1540class RecursorZones(ApiTestCase):
1541
37bc3d01
CH
1542 def create_zone(self, name=None, kind=None, rd=False, servers=None):
1543 if name is None:
1544 name = unique_zone_name()
1545 if servers is None:
1546 servers = []
02945d9a 1547 payload = {
37bc3d01
CH
1548 'name': name,
1549 'kind': kind,
1550 'servers': servers,
1551 'recursion_desired': rd
02945d9a
CH
1552 }
1553 r = self.session.post(
46d06a12 1554 self.url("/api/v1/servers/localhost/zones"),
02945d9a
CH
1555 data=json.dumps(payload),
1556 headers={'content-type': 'application/json'})
c1374bdb
CH
1557 self.assert_success_json(r)
1558 return payload, r.json()
37bc3d01 1559
c1374bdb 1560 def test_create_auth_zone(self):
37bc3d01 1561 payload, data = self.create_zone(kind='Native')
02945d9a
CH
1562 for k in payload.keys():
1563 self.assertEquals(data[k], payload[k])
1564
1d6b70f9 1565 def test_create_zone_no_name(self):
1d6b70f9
CH
1566 payload = {
1567 'name': '',
1568 'kind': 'Native',
1569 'servers': ['8.8.8.8'],
1570 'recursion_desired': False,
1571 }
1572 print payload
1573 r = self.session.post(
1574 self.url("/api/v1/servers/localhost/zones"),
1575 data=json.dumps(payload),
1576 headers={'content-type': 'application/json'})
1577 self.assertEquals(r.status_code, 422)
1578 self.assertIn('is not canonical', r.json()['error'])
1579
c1374bdb 1580 def test_create_forwarded_zone(self):
37bc3d01 1581 payload, data = self.create_zone(kind='Forwarded', rd=False, servers=['8.8.8.8'])
02945d9a
CH
1582 # return values are normalized
1583 payload['servers'][0] += ':53'
02945d9a
CH
1584 for k in payload.keys():
1585 self.assertEquals(data[k], payload[k])
1586
c1374bdb 1587 def test_create_forwarded_rd_zone(self):
1d6b70f9 1588 payload, data = self.create_zone(name='google.com.', kind='Forwarded', rd=True, servers=['8.8.8.8'])
02945d9a
CH
1589 # return values are normalized
1590 payload['servers'][0] += ':53'
02945d9a
CH
1591 for k in payload.keys():
1592 self.assertEquals(data[k], payload[k])
1593
c1374bdb 1594 def test_create_auth_zone_with_symbols(self):
37bc3d01 1595 payload, data = self.create_zone(name='foo/bar.'+unique_zone_name(), kind='Native')
1dbe38ba 1596 expected_id = (payload['name'].replace('/', '=2F'))
02945d9a
CH
1597 for k in payload.keys():
1598 self.assertEquals(data[k], payload[k])
1599 self.assertEquals(data['id'], expected_id)
e2367534 1600
c1374bdb 1601 def test_rename_auth_zone(self):
37bc3d01 1602 payload, data = self.create_zone(kind='Native')
1d6b70f9 1603 name = payload['name']
e2367534
CH
1604 # now rename it
1605 payload = {
1606 'name': 'renamed-'+name,
1607 'kind': 'Native',
1608 'recursion_desired': False
1609 }
1610 r = self.session.put(
46d06a12 1611 self.url("/api/v1/servers/localhost/zones/" + name),
e2367534
CH
1612 data=json.dumps(payload),
1613 headers={'content-type': 'application/json'})
f0e76cee
CH
1614 self.assert_success(r)
1615 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + payload['name'])).json()
e2367534
CH
1616 for k in payload.keys():
1617 self.assertEquals(data[k], payload[k])
37bc3d01 1618
37663c3b
CH
1619 def test_zone_delete(self):
1620 payload, zone = self.create_zone(kind='Native')
1621 name = payload['name']
46d06a12 1622 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
1623 self.assertEquals(r.status_code, 204)
1624 self.assertNotIn('Content-Type', r.headers)
1625
c1374bdb 1626 def test_search_rr_exact_zone(self):
1d6b70f9 1627 name = unique_zone_name()
37bc3d01 1628 self.create_zone(name=name, kind='Native')
46d06a12 1629 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name))
c1374bdb 1630 self.assert_success_json(r)
37bc3d01
CH
1631 print r.json()
1632 self.assertEquals(r.json(), [{u'type': u'zone', u'name': name, u'zone_id': name}])
1633
c1374bdb 1634 def test_search_rr_substring(self):
1d6b70f9 1635 name = 'search-rr-zone.name.'
37bc3d01 1636 self.create_zone(name=name, kind='Native')
46d06a12 1637 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=rr-zone"))
c1374bdb 1638 self.assert_success_json(r)
37bc3d01
CH
1639 print r.json()
1640 # should return zone, SOA
1641 self.assertEquals(len(r.json()), 2)
ccfabd0d 1642
ccfabd0d
CH
1643@unittest.skipIf(not is_auth(), "Not applicable")
1644class AuthZoneKeys(ApiTestCase, AuthZonesHelperMixin):
1645
1646 def test_get_keys(self):
1647 r = self.session.get(
1648 self.url("/api/v1/servers/localhost/zones/powerdnssec.org./cryptokeys"))
1649 self.assert_success_json(r)
1650 keys = r.json()
1651 self.assertGreater(len(keys), 0)
1652
1653 key0 = deepcopy(keys[0])
1654 del key0['dnskey']
b6bd795c 1655 del key0['ds']
ccfabd0d 1656 expected = {
5d9c6182
PL
1657 u'algorithm': u'ECDSAP256SHA256',
1658 u'bits': 256,
ccfabd0d
CH
1659 u'active': True,
1660 u'type': u'Cryptokey',
b6bd795c
PL
1661 u'keytype': u'csk',
1662 u'flags': 257,
ccfabd0d
CH
1663 u'id': 1}
1664 self.assertEquals(key0, expected)
1665
1666 keydata = keys[0]['dnskey'].split()
1667 self.assertEqual(len(keydata), 4)