]>
Commit | Line | Data |
---|---|---|
541bb91b | 1 | from __future__ import print_function |
e2dba705 | 2 | import json |
486210b8 | 3 | import operator |
d29d5db7 | 4 | import time |
e2dba705 | 5 | import unittest |
f626cc48 | 6 | import requests.exceptions |
ccfabd0d | 7 | from copy import deepcopy |
646bcd7d | 8 | from parameterized import parameterized |
6754ef71 | 9 | from pprint import pprint |
168a76b3 | 10 | from test_helper import ApiTestCase, unique_zone_name, is_auth, is_auth_lmdb, is_recursor, get_db_records, pdnsutil_rectify, sdig |
6754ef71 CH |
11 | |
12 | ||
13 | def 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 | ||
20 | def 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 | ||
27 | def 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 |
52 | def 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 |
58 | def 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 | 78 | class 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 | 112 | class 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") | |
194 | class 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 | |
962 | flags: 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: | |
971 | powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
972 | powerdns-broken.com. 3600 IN NS powerdnssec2.ds9a.nl. | |
973 | powerdns-broken.com. 3600 IN AAAA 2001:888:2000:1d::2 | |
974 | powerdns-broken.com. 86400 IN A 82.94.213.34 | |
975 | powerdns-broken.com. 3600 IN MX 0 xs.powerdns.com. | |
976 | powerdns-broken.com. 3600 IN NS powerdnssec1.ds9a.nl. | |
977 | powerdns-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 | 995 | example.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: | |
1023 | powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800 | |
1024 | powerdns.com. 3600 IN NS powerdnssec2.ds9a.nl. | |
1025 | powerdns.com. 3600 IN AAAA 2001:888:2000:1d::2 | |
1026 | powerdns.com. 86400 IN A 82.94.213.34 | |
1027 | powerdns.com. 3600 IN MX 0 xs.powerdns.com. | |
1028 | powerdns.com. 3600 IN NS powerdnssec1.ds9a.nl. | |
1029 | powerdns.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 | 1088 | ns1 IN A 192.168.0.1 ;name server definition |
0f0e73fe MS |
1089 | www IN A 192.168.0.2 ;web server definition |
1090 | ftp IN CNAME www.example.org. ;ftp server definition | |
1091 | ; non server domain hosts | |
1092 | bill IN A 192.168.0.3 | |
1d6b70f9 | 1093 | fred 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") |
2484 | class 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 |
2547 | class 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") |
2651 | class 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) |