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