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