]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.api/test_Zones.py
Permit star, fixes #3406
[thirdparty/pdns.git] / regression-tests.api / test_Zones.py
CommitLineData
e2dba705 1import json
d29d5db7 2import time
e2dba705 3import unittest
ccfabd0d 4from copy import deepcopy
1d6b70f9 5from test_helper import ApiTestCase, unique_zone_name, is_auth, is_recursor, eq_zone_dict, get_db_records
1a152698
CH
6
7
02945d9a 8class Zones(ApiTestCase):
1a152698 9
c1374bdb 10 def test_list_zones(self):
46d06a12 11 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
c1374bdb 12 self.assert_success_json(r)
45de6290 13 domains = r.json()
02945d9a 14 example_com = [domain for domain in domains if domain['name'] in ('example.com', 'example.com.')]
1a152698
CH
15 self.assertEquals(len(example_com), 1)
16 example_com = example_com[0]
02945d9a 17 required_fields = ['id', 'url', 'name', 'kind']
c1374bdb 18 if is_auth():
c04b5870 19 required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account']
c1374bdb 20 elif is_recursor():
02945d9a
CH
21 required_fields = required_fields + ['recursion_desired', 'servers']
22 for field in required_fields:
23 self.assertIn(field, example_com)
24
25
406497f5 26class AuthZonesHelperMixin(object):
284fdfe9 27 def create_zone(self, name=None, **kwargs):
bee2acae
CH
28 if name is None:
29 name = unique_zone_name()
e2dba705 30 payload = {
bee2acae 31 'name': name,
e2dba705 32 'kind': 'Native',
1d6b70f9 33 'nameservers': ['ns1.example.com.', 'ns2.example.com.']
e2dba705 34 }
284fdfe9 35 for k, v in kwargs.items():
4bdff352
CH
36 if v is None:
37 del payload[k]
38 else:
39 payload[k] = v
1d6b70f9 40 print "sending", payload
e2dba705 41 r = self.session.post(
46d06a12 42 self.url("/api/v1/servers/localhost/zones"),
e2dba705
CH
43 data=json.dumps(payload),
44 headers={'content-type': 'application/json'})
c1374bdb 45 self.assert_success_json(r)
64a36f0d 46 self.assertEquals(r.status_code, 201)
1d6b70f9
CH
47 reply = r.json()
48 print "reply", reply
49 return payload, reply
bee2acae 50
406497f5
CH
51
52@unittest.skipIf(not is_auth(), "Not applicable")
53class AuthZones(ApiTestCase, AuthZonesHelperMixin):
54
c1374bdb 55 def test_create_zone(self):
b0af9105
CH
56 # soa_edit_api has a default, override with empty for this test
57 payload, data = self.create_zone(serial=22, soa_edit_api='')
8ffb7a9b 58 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'):
d29d5db7
CH
59 self.assertIn(k, data)
60 if k in payload:
61 self.assertEquals(data[k], payload[k])
62 self.assertEquals(data['comments'], [])
f63168e6 63 # validate generated SOA
1d6b70f9
CH
64 expected_soa = "a.misconfigured.powerdns.server. hostmaster." + payload['name'] + " " + \
65 str(payload['serial']) + " 10800 3600 604800 3600"
f63168e6
CH
66 self.assertEquals(
67 [r['content'] for r in data['records'] if r['type'] == 'SOA'][0],
1d6b70f9 68 expected_soa
f63168e6 69 )
1d6b70f9
CH
70 # Because we had confusion about dots, check that the DB is without dots.
71 dbrecs = get_db_records(payload['name'], 'SOA')
72 self.assertEqual(dbrecs[0]['content'], expected_soa.replace('. ', ' '))
d29d5db7 73
c1374bdb 74 def test_create_zone_with_soa_edit_api(self):
f63168e6
CH
75 # soa_edit_api wins over serial
76 payload, data = self.create_zone(soa_edit_api='EPOCH', serial=10)
77 for k in ('soa_edit_api', ):
e2dba705
CH
78 self.assertIn(k, data)
79 if k in payload:
80 self.assertEquals(data[k], payload[k])
f63168e6
CH
81 # generated EPOCH serial surely is > fixed serial we passed in
82 print data
83 self.assertGreater(data['serial'], payload['serial'])
84 soa_serial = int([r['content'].split(' ')[2] for r in data['records'] if r['type'] == 'SOA'][0])
85 self.assertGreater(soa_serial, payload['serial'])
86 self.assertEquals(soa_serial, data['serial'])
6bb25159 87
79532aa7
CH
88 def test_create_zone_with_account(self):
89 # soa_edit_api wins over serial
90 payload, data = self.create_zone(account='anaccount', serial=10)
91 print data
92 for k in ('account', ):
93 self.assertIn(k, data)
94 if k in payload:
95 self.assertEquals(data[k], payload[k])
96
c1374bdb 97 def test_create_zone_with_records(self):
f63168e6
CH
98 name = unique_zone_name()
99 records = [
100 {
101 "name": name,
102 "type": "A",
f63168e6
CH
103 "ttl": 3600,
104 "content": "4.3.2.1",
105 "disabled": False
106 }
107 ]
108 payload, data = self.create_zone(name=name, records=records)
109 # check our record has appeared
110 self.assertEquals([r for r in data['records'] if r['type'] == records[0]['type']], records)
111
c1374bdb 112 def test_create_zone_with_comments(self):
f63168e6
CH
113 name = unique_zone_name()
114 comments = [
115 {
116 'name': name,
8ce0dc75 117 'type': 'soa', # test uppercasing of type, too.
f63168e6
CH
118 'account': 'test1',
119 'content': 'blah blah',
120 'modified_at': 11112,
121 }
122 ]
123 payload, data = self.create_zone(name=name, comments=comments)
8ce0dc75 124 comments[0]['type'] = comments[0]['type'].upper()
f63168e6
CH
125 # check our comment has appeared
126 self.assertEquals(data['comments'], comments)
127
1d6b70f9
CH
128 def test_create_zone_uncanonical_nameservers(self):
129 name = unique_zone_name()
130 payload = {
131 'name': name,
132 'kind': 'Native',
133 'nameservers': ['uncanon.example.com']
134 }
135 print payload
136 r = self.session.post(
137 self.url("/api/v1/servers/localhost/zones"),
138 data=json.dumps(payload),
139 headers={'content-type': 'application/json'})
140 self.assertEquals(r.status_code, 422)
141 self.assertIn('Nameserver is not canonical', r.json()['error'])
142
143 def test_create_auth_zone_no_name(self):
144 name = unique_zone_name()
145 payload = {
146 'name': '',
147 'kind': 'Native',
148 }
149 print payload
150 r = self.session.post(
151 self.url("/api/v1/servers/localhost/zones"),
152 data=json.dumps(payload),
153 headers={'content-type': 'application/json'})
154 self.assertEquals(r.status_code, 422)
155 self.assertIn('is not canonical', r.json()['error'])
156
c1374bdb 157 def test_create_zone_with_custom_soa(self):
f63168e6
CH
158 name = unique_zone_name()
159 records = [
160 {
1d6b70f9
CH
161 u"name": name,
162 u"type": u"soa", # test uppercasing of type, too.
163 u"ttl": 3600,
164 u"content": u"ns1.example.net. testmaster@example.net. 10 10800 3600 604800 3600",
165 u"disabled": False
f63168e6
CH
166 }
167 ]
1d6b70f9 168 payload, data = self.create_zone(name=name, records=records, soa_edit_api='')
8ce0dc75 169 records[0]['type'] = records[0]['type'].upper()
f63168e6 170 self.assertEquals([r for r in data['records'] if r['type'] == records[0]['type']], records)
1d6b70f9
CH
171 dbrecs = get_db_records(name, records[0]['type'])
172 self.assertEqual(dbrecs[0]['content'], records[0]['content'].replace('. ', ' '))
173
174 def test_create_zone_double_dot(self):
175 name = 'test..' + unique_zone_name()
176 payload = {
177 'name': name,
178 'kind': 'Native',
179 'nameservers': ['ns1.example.com.']
180 }
181 print payload
182 r = self.session.post(
183 self.url("/api/v1/servers/localhost/zones"),
184 data=json.dumps(payload),
185 headers={'content-type': 'application/json'})
186 self.assertEquals(r.status_code, 422)
187 self.assertIn('Unable to parse DNS Name', r.json()['error'])
05776d2f 188
1d6b70f9
CH
189 def test_create_zone_restricted_chars(self):
190 name = 'test:' + unique_zone_name() # : isn't good as a name.
191 payload = {
192 'name': name,
193 'kind': 'Native',
194 'nameservers': ['ns1.example.com']
195 }
196 print payload
197 r = self.session.post(
198 self.url("/api/v1/servers/localhost/zones"),
199 data=json.dumps(payload),
200 headers={'content-type': 'application/json'})
201 self.assertEquals(r.status_code, 422)
202 self.assertIn('contains unsupported characters', r.json()['error'])
4ebf78b1 203
c1374bdb 204 def test_create_zone_with_symbols(self):
bee2acae
CH
205 payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
206 name = payload['name']
1d6b70f9 207 expected_id = name.replace('/', '=2F')
00a9b229
CH
208 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
209 self.assertIn(k, data)
210 if k in payload:
211 self.assertEquals(data[k], payload[k])
bee2acae 212 self.assertEquals(data['id'], expected_id)
1d6b70f9
CH
213 dbrecs = get_db_records(name, 'SOA')
214 self.assertEqual(dbrecs[0]['name'], name.rstrip('.'))
00a9b229 215
c1374bdb 216 def test_create_zone_with_nameservers_non_string(self):
e90b4e38
CH
217 # ensure we don't crash
218 name = unique_zone_name()
219 payload = {
220 'name': name,
221 'kind': 'Native',
222 'nameservers': [{'a': 'ns1.example.com'}] # invalid
223 }
224 print payload
225 r = self.session.post(
46d06a12 226 self.url("/api/v1/servers/localhost/zones"),
e90b4e38
CH
227 data=json.dumps(payload),
228 headers={'content-type': 'application/json'})
229 self.assertEquals(r.status_code, 422)
230
4bdff352
CH
231 def test_create_slave_zone(self):
232 # Test that nameservers can be absent for slave zones.
233 payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
234 for k in ('name', 'masters', 'kind'):
235 self.assertIn(k, data)
236 self.assertEquals(data[k], payload[k])
4de11a54
CH
237 print "payload:", payload
238 print "data:", data
239 # Because slave zones don't get a SOA, we need to test that they'll show up in the zone list.
46d06a12 240 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
4de11a54
CH
241 zonelist = r.json()
242 print "zonelist:", zonelist
243 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
244 # Also test that fetching the zone works.
46d06a12 245 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54
CH
246 data = r.json()
247 print "zone (fetched):", data
248 for k in ('name', 'masters', 'kind'):
249 self.assertIn(k, data)
250 self.assertEquals(data[k], payload[k])
251 self.assertEqual(data['serial'], 0)
252 self.assertEqual(data['records'], [])
253
254 def test_delete_slave_zone(self):
255 payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
46d06a12 256 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54 257 r.raise_for_status()
4bdff352 258
a426cb89
CH
259 def test_retrieve_slave_zone(self):
260 payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
261 print "payload:", payload
262 print "data:", data
46d06a12 263 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/axfr-retrieve"))
a426cb89
CH
264 data = r.json()
265 print "status for axfr-retrieve:", data
266 self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
267 '\' from master 127.0.0.2')
268
269 def test_notify_master_zone(self):
270 payload, data = self.create_zone(kind='Master')
271 print "payload:", payload
272 print "data:", data
46d06a12 273 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/notify"))
a426cb89
CH
274 data = r.json()
275 print "status for notify:", data
276 self.assertEqual(data['result'], 'Notification queued')
277
c1374bdb 278 def test_get_zone_with_symbols(self):
3c3c006b
CH
279 payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
280 name = payload['name']
1d6b70f9 281 zone_id = (name.replace('/', '=2F'))
46d06a12 282 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id))
c1374bdb 283 data = r.json()
6bb25159 284 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'):
3c3c006b
CH
285 self.assertIn(k, data)
286 if k in payload:
287 self.assertEquals(data[k], payload[k])
288
c1374bdb 289 def test_get_zone(self):
46d06a12 290 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
05776d2f 291 domains = r.json()
1d6b70f9 292 example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
46d06a12 293 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id']))
c1374bdb 294 self.assert_success_json(r)
05776d2f
CH
295 data = r.json()
296 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
297 self.assertIn(k, data)
1d6b70f9 298 self.assertEquals(data['name'], 'example.com.')
7c0ba3d2 299
0f0e73fe
MS
300 def test_import_zone_broken(self):
301 payload = {}
302 payload['zone'] = """
303;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
304flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
305;; WARNING: recursion requested but not available
306
307;; OPT PSEUDOSECTION:
308; EDNS: version: 0, flags:; udp: 1680
309;; QUESTION SECTION:
310;powerdns.com. IN SOA
311
312;; ANSWER SECTION:
313powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
314powerdns-broken.com. 3600 IN NS powerdnssec2.ds9a.nl.
315powerdns-broken.com. 3600 IN AAAA 2001:888:2000:1d::2
316powerdns-broken.com. 86400 IN A 82.94.213.34
317powerdns-broken.com. 3600 IN MX 0 xs.powerdns.com.
318powerdns-broken.com. 3600 IN NS powerdnssec1.ds9a.nl.
319powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
320"""
1d6b70f9 321 payload['name'] = 'powerdns-broken.com.'
0f0e73fe
MS
322 payload['kind'] = 'Master'
323 payload['nameservers'] = []
324 r = self.session.post(
46d06a12 325 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
326 data=json.dumps(payload),
327 headers={'content-type': 'application/json'})
328 self.assertEquals(r.status_code, 422)
329
1d6b70f9
CH
330 def test_import_zone_axfr_outofzone(self):
331 # Ensure we don't create out-of-zone records
332 name = unique_zone_name()
333 payload = {}
334 payload['zone'] = """
335NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
336NAME 3600 IN NS powerdnssec2.ds9a.nl.
337example.org. 3600 IN AAAA 2001:888:2000:1d::2
338NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
339""".replace('NAME', name)
340 payload['name'] = name
341 payload['kind'] = 'Master'
342 payload['nameservers'] = []
343 r = self.session.post(
344 self.url("/api/v1/servers/localhost/zones"),
345 data=json.dumps(payload),
346 headers={'content-type': 'application/json'})
347 self.assertEquals(r.status_code, 422)
348 self.assertEqual(r.json()['error'], 'RRset example.org. IN AAAA: Name is out of zone')
349
0f0e73fe
MS
350 def test_import_zone_axfr(self):
351 payload = {}
352 payload['zone'] = """
353;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
354;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
355;; WARNING: recursion requested but not available
356
357;; OPT PSEUDOSECTION:
358; EDNS: version: 0, flags:; udp: 1680
359;; QUESTION SECTION:
360;powerdns.com. IN SOA
361
362;; ANSWER SECTION:
363powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
364powerdns.com. 3600 IN NS powerdnssec2.ds9a.nl.
365powerdns.com. 3600 IN AAAA 2001:888:2000:1d::2
366powerdns.com. 86400 IN A 82.94.213.34
367powerdns.com. 3600 IN MX 0 xs.powerdns.com.
368powerdns.com. 3600 IN NS powerdnssec1.ds9a.nl.
369powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
370"""
1d6b70f9 371 payload['name'] = 'powerdns.com.'
0f0e73fe
MS
372 payload['kind'] = 'Master'
373 payload['nameservers'] = []
1d6b70f9 374 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 375 r = self.session.post(
46d06a12 376 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
377 data=json.dumps(payload),
378 headers={'content-type': 'application/json'})
379 self.assert_success_json(r)
380 data = r.json()
381 self.assertIn('name', data)
382 self.assertIn('records', data)
383
90568eb2
MS
384 expected = {
385 'NS': [
386 { 'content': 'powerdnssec1.ds9a.nl.' },
387 { 'content': 'powerdnssec2.ds9a.nl.' } ],
388 'SOA': [
389 { 'content': 'powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800' } ],
390 'MX': [
05cf6a71 391 { 'content': '0 xs.powerdns.com.' } ],
90568eb2 392 'A': [
1d6b70f9 393 { 'content': '82.94.213.34', 'name': 'powerdns.com.' } ],
90568eb2 394 'AAAA': [
1d6b70f9 395 { 'content': '2001:888:2000:1d::2', 'name': 'powerdns.com.' } ]
90568eb2 396 }
0f0e73fe 397
1d6b70f9
CH
398 eq_zone_dict(data['records'], expected)
399
400 # noDot check
401 dbrecs = get_db_records(payload['name'], 'NS')
402 self.assertEqual(dbrecs[0]['content'], 'powerdnssec2.ds9a.nl')
0f0e73fe
MS
403
404 def test_import_zone_bind(self):
405 payload = {}
406 payload['zone'] = """
407$TTL 86400 ; 24 hours could have been written as 24h or 1d
408; $TTL used for all RRs without explicit TTL value
409$ORIGIN example.org.
410@ 1D IN SOA ns1.example.org. hostmaster.example.org. (
411 2002022401 ; serial
412 3H ; refresh
413 15 ; retry
414 1w ; expire
415 3h ; minimum
416 )
417 IN NS ns1.example.org. ; in the domain
418 IN NS ns2.smokeyjoe.com. ; external to domain
419 IN MX 10 mail.another.com. ; external mail provider
420; server host definitions
1d6b70f9 421ns1 IN A 192.168.0.1 ;name server definition
0f0e73fe
MS
422www IN A 192.168.0.2 ;web server definition
423ftp IN CNAME www.example.org. ;ftp server definition
424; non server domain hosts
425bill IN A 192.168.0.3
1d6b70f9 426fred IN A 192.168.0.4
0f0e73fe 427"""
1d6b70f9 428 payload['name'] = 'example.org.'
0f0e73fe
MS
429 payload['kind'] = 'Master'
430 payload['nameservers'] = []
1d6b70f9 431 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 432 r = self.session.post(
46d06a12 433 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
434 data=json.dumps(payload),
435 headers={'content-type': 'application/json'})
436 self.assert_success_json(r)
437 data = r.json()
438 self.assertIn('name', data)
439 self.assertIn('records', data)
440
90568eb2
MS
441 expected = {
442 'NS': [
443 { 'content': 'ns1.example.org.' },
444 { 'content': 'ns2.smokeyjoe.com.' } ],
445 'SOA': [
446 { 'content': 'ns1.example.org. hostmaster.example.org. 2002022401 10800 15 604800 10800' } ],
447 'MX': [
05cf6a71 448 { 'content': '10 mail.another.com.' } ],
90568eb2 449 'A': [
1d6b70f9
CH
450 { 'content': '192.168.0.1', 'name': 'ns1.example.org.' },
451 { 'content': '192.168.0.2', 'name': 'www.example.org.' },
452 { 'content': '192.168.0.3', 'name': 'bill.example.org.' },
453 { 'content': '192.168.0.4', 'name': 'fred.example.org.' } ],
90568eb2 454 'CNAME': [
1d6b70f9 455 { 'content': 'www.example.org.', 'name': 'ftp.example.org.' } ]
90568eb2 456 }
0f0e73fe 457
1d6b70f9 458 eq_zone_dict(data['records'], expected)
0f0e73fe 459
c1374bdb 460 def test_export_zone_json(self):
1d6b70f9 461 payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
462 name = payload['name']
463 # export it
464 r = self.session.get(
46d06a12 465 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
466 headers={'accept': 'application/json;q=0.9,*/*;q=0.8'}
467 )
c1374bdb 468 self.assert_success_json(r)
a83004d3
CH
469 data = r.json()
470 self.assertIn('zone', data)
1d6b70f9
CH
471 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
472 name + '\t3600\tNS\tns2.foo.com.',
473 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
474 ' 0 10800 3600 604800 3600']
a83004d3
CH
475 self.assertEquals(data['zone'].strip().split('\n'), expected_data)
476
c1374bdb 477 def test_export_zone_text(self):
1d6b70f9 478 payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
479 name = payload['name']
480 # export it
481 r = self.session.get(
46d06a12 482 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
483 headers={'accept': '*/*'}
484 )
485 data = r.text.strip().split("\n")
1d6b70f9
CH
486 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
487 name + '\t3600\tNS\tns2.foo.com.',
488 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
489 ' 0 10800 3600 604800 3600']
a83004d3
CH
490 self.assertEquals(data, expected_data)
491
c1374bdb 492 def test_update_zone(self):
bee2acae
CH
493 payload, zone = self.create_zone()
494 name = payload['name']
d29d5db7 495 # update, set as Master and enable SOA-EDIT-API
7c0ba3d2
CH
496 payload = {
497 'kind': 'Master',
c1374bdb 498 'masters': ['192.0.2.1', '192.0.2.2'],
6bb25159
MS
499 'soa_edit_api': 'EPOCH',
500 'soa_edit': 'EPOCH'
7c0ba3d2
CH
501 }
502 r = self.session.put(
46d06a12 503 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
504 data=json.dumps(payload),
505 headers={'content-type': 'application/json'})
c1374bdb 506 self.assert_success_json(r)
7c0ba3d2
CH
507 data = r.json()
508 for k in payload.keys():
509 self.assertIn(k, data)
510 self.assertEquals(data[k], payload[k])
d29d5db7 511 # update, back to Native and empty(off)
7c0ba3d2 512 payload = {
d29d5db7 513 'kind': 'Native',
6bb25159
MS
514 'soa_edit_api': '',
515 'soa_edit': ''
7c0ba3d2
CH
516 }
517 r = self.session.put(
46d06a12 518 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
519 data=json.dumps(payload),
520 headers={'content-type': 'application/json'})
c1374bdb 521 self.assert_success_json(r)
7c0ba3d2
CH
522 data = r.json()
523 for k in payload.keys():
524 self.assertIn(k, data)
525 self.assertEquals(data[k], payload[k])
b3905a3d 526
c1374bdb 527 def test_zone_rr_update(self):
bee2acae
CH
528 payload, zone = self.create_zone()
529 name = payload['name']
b3905a3d 530 # do a replace (= update)
d708640f 531 rrset = {
b3905a3d
CH
532 'changetype': 'replace',
533 'name': name,
8ce0dc75 534 'type': 'ns',
b3905a3d
CH
535 'records': [
536 {
537 "name": name,
538 "type": "NS",
b3905a3d 539 "ttl": 3600,
1d6b70f9 540 "content": "ns1.bar.com.",
cea26350
CH
541 "disabled": False
542 },
543 {
544 "name": name,
545 "type": "NS",
cea26350 546 "ttl": 1800,
1d6b70f9 547 "content": "ns2-disabled.bar.com.",
cea26350 548 "disabled": True
b3905a3d
CH
549 }
550 ]
551 }
d708640f 552 payload = {'rrsets': [rrset]}
b3905a3d 553 r = self.session.patch(
46d06a12 554 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
555 data=json.dumps(payload),
556 headers={'content-type': 'application/json'})
c1374bdb 557 self.assert_success_json(r)
b3905a3d 558 # verify that (only) the new record is there
46d06a12 559 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
8ce0dc75 560 rrset['type'] = rrset['type'].upper()
b3905a3d 561 data = r.json()['records']
1d6b70f9 562 recs = [rec for rec in data if rec['type'].upper() == rrset['type'].upper() and rec['name'].upper() == rrset['name'].upper()]
d708640f 563 self.assertEquals(recs, rrset['records'])
b3905a3d 564
c1374bdb 565 def test_zone_rr_update_mx(self):
05cf6a71 566 # Important to test with MX records, as they have a priority field, which must end up in the content field.
41e3b10e
CH
567 payload, zone = self.create_zone()
568 name = payload['name']
569 # do a replace (= update)
d708640f 570 rrset = {
41e3b10e
CH
571 'changetype': 'replace',
572 'name': name,
573 'type': 'MX',
574 'records': [
575 {
576 "name": name,
577 "type": "MX",
41e3b10e 578 "ttl": 3600,
1d6b70f9 579 "content": "10 mail.example.org.",
41e3b10e
CH
580 "disabled": False
581 }
582 ]
583 }
d708640f 584 payload = {'rrsets': [rrset]}
41e3b10e 585 r = self.session.patch(
46d06a12 586 self.url("/api/v1/servers/localhost/zones/" + name),
41e3b10e
CH
587 data=json.dumps(payload),
588 headers={'content-type': 'application/json'})
c1374bdb 589 self.assert_success_json(r)
41e3b10e 590 # verify that (only) the new record is there
46d06a12 591 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
41e3b10e 592 data = r.json()['records']
d708640f
CH
593 recs = [rec for rec in data if rec['type'] == rrset['type'] and rec['name'] == rrset['name']]
594 self.assertEquals(recs, rrset['records'])
595
c1374bdb 596 def test_zone_rr_update_multiple_rrsets(self):
d708640f
CH
597 payload, zone = self.create_zone()
598 name = payload['name']
599 rrset1 = {
600 'changetype': 'replace',
601 'name': name,
602 'type': 'NS',
603 'records': [
604 {
605 "name": name,
606 "type": "NS",
d708640f 607 "ttl": 3600,
1d6b70f9 608 "content": "ns9999.example.com.",
d708640f
CH
609 "disabled": False
610 }
611 ]
612 }
613 rrset2 = {
614 'changetype': 'replace',
615 'name': name,
616 'type': 'MX',
617 'records': [
618 {
619 "name": name,
620 "type": "MX",
d708640f 621 "ttl": 3600,
1d6b70f9 622 "content": "10 mx444.example.com.",
d708640f
CH
623 "disabled": False
624 }
625 ]
626 }
627 payload = {'rrsets': [rrset1, rrset2]}
628 r = self.session.patch(
46d06a12 629 self.url("/api/v1/servers/localhost/zones/" + name),
d708640f
CH
630 data=json.dumps(payload),
631 headers={'content-type': 'application/json'})
c1374bdb 632 self.assert_success_json(r)
d708640f 633 # verify that all rrsets have been updated
46d06a12 634 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
d708640f
CH
635 data = r.json()['records']
636 recs1 = [rec for rec in data if rec['type'] == rrset1['type'] and rec['name'] == rrset1['name']]
637 self.assertEquals(recs1, rrset1['records'])
638 recs2 = [rec for rec in data if rec['type'] == rrset2['type'] and rec['name'] == rrset2['name']]
639 self.assertEquals(recs2, rrset2['records'])
41e3b10e 640
c1374bdb 641 def test_zone_rr_delete(self):
bee2acae
CH
642 payload, zone = self.create_zone()
643 name = payload['name']
b3905a3d 644 # do a delete of all NS records (these are created with the zone)
d708640f 645 rrset = {
b3905a3d
CH
646 'changetype': 'delete',
647 'name': name,
648 'type': 'NS'
649 }
d708640f 650 payload = {'rrsets': [rrset]}
b3905a3d 651 r = self.session.patch(
46d06a12 652 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
653 data=json.dumps(payload),
654 headers={'content-type': 'application/json'})
c1374bdb 655 self.assert_success_json(r)
b3905a3d 656 # verify that the records are gone
46d06a12 657 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
b3905a3d 658 data = r.json()['records']
d708640f 659 recs = [rec for rec in data if rec['type'] == rrset['type'] and rec['name'] == rrset['name']]
b3905a3d 660 self.assertEquals(recs, [])
cea26350 661
c1374bdb 662 def test_zone_disable_reenable(self):
d29d5db7
CH
663 # This also tests that SOA-EDIT-API works.
664 payload, zone = self.create_zone(soa_edit_api='EPOCH')
cea26350
CH
665 name = payload['name']
666 # disable zone by disabling SOA
d708640f 667 rrset = {
cea26350
CH
668 'changetype': 'replace',
669 'name': name,
670 'type': 'SOA',
671 'records': [
672 {
673 "name": name,
674 "type": "SOA",
cea26350 675 "ttl": 3600,
1d6b70f9 676 "content": "ns1.bar.com. hostmaster.foo.org. 1 1 1 1 1",
cea26350
CH
677 "disabled": True
678 }
679 ]
680 }
d708640f 681 payload = {'rrsets': [rrset]}
cea26350 682 r = self.session.patch(
46d06a12 683 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
684 data=json.dumps(payload),
685 headers={'content-type': 'application/json'})
c1374bdb 686 self.assert_success_json(r)
d29d5db7
CH
687 # check SOA serial has been edited
688 print r.json()
689 soa_serial1 = [rec for rec in r.json()['records'] if rec['type'] == 'SOA'][0]['content'].split()[2]
690 self.assertNotEquals(soa_serial1, '1')
691 # make sure domain is still in zone list (disabled SOA!)
46d06a12 692 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
cea26350
CH
693 domains = r.json()
694 self.assertEquals(len([domain for domain in domains if domain['name'] == name]), 1)
d29d5db7
CH
695 # sleep 1sec to ensure the EPOCH value changes for the next request
696 time.sleep(1)
cea26350 697 # verify that modifying it still works
d708640f
CH
698 rrset['records'][0]['disabled'] = False
699 payload = {'rrsets': [rrset]}
cea26350 700 r = self.session.patch(
46d06a12 701 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
702 data=json.dumps(payload),
703 headers={'content-type': 'application/json'})
c1374bdb 704 self.assert_success_json(r)
d29d5db7
CH
705 # check SOA serial has been edited again
706 print r.json()
707 soa_serial2 = [rec for rec in r.json()['records'] if rec['type'] == 'SOA'][0]['content'].split()[2]
708 self.assertNotEquals(soa_serial2, '1')
709 self.assertNotEquals(soa_serial2, soa_serial1)
02945d9a 710
c1374bdb 711 def test_zone_rr_update_qtype_mismatch(self):
35f26cc5
CH
712 payload, zone = self.create_zone()
713 name = payload['name']
714 # replace with qtype mismatch
d708640f 715 rrset = {
35f26cc5
CH
716 'changetype': 'replace',
717 'name': name,
718 'type': 'A',
719 'records': [
720 {
721 "name": name,
722 "type": "NS",
35f26cc5 723 "ttl": 3600,
1d6b70f9 724 "content": "ns1.bar.com.",
35f26cc5
CH
725 "disabled": False
726 }
727 ]
728 }
d708640f 729 payload = {'rrsets': [rrset]}
35f26cc5 730 r = self.session.patch(
46d06a12 731 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
732 data=json.dumps(payload),
733 headers={'content-type': 'application/json'})
734 self.assertEquals(r.status_code, 422)
735
c1374bdb 736 def test_zone_rr_update_qname_mismatch(self):
35f26cc5
CH
737 payload, zone = self.create_zone()
738 name = payload['name']
739 # replace with qname mismatch
d708640f 740 rrset = {
35f26cc5
CH
741 'changetype': 'replace',
742 'name': name,
743 'type': 'NS',
744 'records': [
745 {
746 "name": 'blah.'+name,
747 "type": "NS",
35f26cc5 748 "ttl": 3600,
1d6b70f9 749 "content": "ns1.bar.com.",
35f26cc5
CH
750 "disabled": False
751 }
752 ]
753 }
d708640f 754 payload = {'rrsets': [rrset]}
35f26cc5 755 r = self.session.patch(
46d06a12 756 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
757 data=json.dumps(payload),
758 headers={'content-type': 'application/json'})
759 self.assertEquals(r.status_code, 422)
760
c1374bdb 761 def test_zone_rr_update_out_of_zone(self):
35f26cc5
CH
762 payload, zone = self.create_zone()
763 name = payload['name']
764 # replace with qname mismatch
d708640f 765 rrset = {
35f26cc5 766 'changetype': 'replace',
1d6b70f9 767 'name': 'not-in-zone.',
35f26cc5
CH
768 'type': 'NS',
769 'records': [
770 {
771 "name": name,
772 "type": "NS",
35f26cc5 773 "ttl": 3600,
1d6b70f9 774 "content": "ns1.bar.com.",
35f26cc5
CH
775 "disabled": False
776 }
777 ]
778 }
d708640f 779 payload = {'rrsets': [rrset]}
35f26cc5 780 r = self.session.patch(
46d06a12 781 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
782 data=json.dumps(payload),
783 headers={'content-type': 'application/json'})
784 self.assertEquals(r.status_code, 422)
785 self.assertIn('out of zone', r.json()['error'])
786
1d6b70f9
CH
787 def test_zone_rr_update_restricted_chars(self):
788 payload, zone = self.create_zone()
789 name = payload['name']
790 # replace with qname mismatch
791 rrset = {
792 'changetype': 'replace',
793 'name': 'test:' + name,
794 'type': 'NS',
795 'records': [
796 {
797 "name": 'test:' + name,
798 "type": "NS",
799 "ttl": 3600,
800 "content": "ns1.bar.com.",
801 "disabled": False
802 }
803 ]
804 }
805 payload = {'rrsets': [rrset]}
806 r = self.session.patch(
807 self.url("/api/v1/servers/localhost/zones/" + name),
808 data=json.dumps(payload),
809 headers={'content-type': 'application/json'})
810 self.assertEquals(r.status_code, 422)
811 self.assertIn('contains unsupported characters', r.json()['error'])
812
24cd86ca
CH
813 def test_rrset_unknown_type(self):
814 payload, zone = self.create_zone()
815 name = payload['name']
816 rrset = {
817 'changetype': 'replace',
818 'name': name,
819 'type': 'FAFAFA',
820 'records': [
821 {
822 "name": name,
823 "type": "FAFAFA",
824 "ttl": 3600,
825 "content": "4.3.2.1",
826 "disabled": False
827 }
828 ]
829 }
830 payload = {'rrsets': [rrset]}
46d06a12 831 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
24cd86ca
CH
832 headers={'content-type': 'application/json'})
833 self.assertEquals(r.status_code, 422)
834 self.assertIn('unknown type', r.json()['error'])
835
1e5b9ab9
CH
836 def test_create_zone_with_leading_space(self):
837 # Actual regression.
838 payload, zone = self.create_zone()
839 name = payload['name']
840 rrset = {
841 'changetype': 'replace',
842 'name': name,
843 'type': 'A',
844 'records': [
845 {
846 "name": name,
847 "type": "A",
848 "ttl": 3600,
849 "content": " 4.3.2.1",
850 "disabled": False
851 }
852 ]
853 }
854 payload = {'rrsets': [rrset]}
46d06a12 855 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1e5b9ab9
CH
856 headers={'content-type': 'application/json'})
857 self.assertEquals(r.status_code, 422)
858 self.assertIn('Not in expected format', r.json()['error'])
859
c1374bdb 860 def test_zone_rr_delete_out_of_zone(self):
35f26cc5
CH
861 payload, zone = self.create_zone()
862 name = payload['name']
d708640f 863 rrset = {
35f26cc5 864 'changetype': 'delete',
1d6b70f9 865 'name': 'not-in-zone.',
35f26cc5
CH
866 'type': 'NS'
867 }
d708640f 868 payload = {'rrsets': [rrset]}
35f26cc5 869 r = self.session.patch(
46d06a12 870 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
871 data=json.dumps(payload),
872 headers={'content-type': 'application/json'})
34df6ecc
CH
873 print r.content
874 self.assertEquals(r.status_code, 200) # succeed so users can fix their wrong, old data
35f26cc5 875
37663c3b
CH
876 def test_zone_delete(self):
877 payload, zone = self.create_zone()
878 name = payload['name']
46d06a12 879 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
880 self.assertEquals(r.status_code, 204)
881 self.assertNotIn('Content-Type', r.headers)
882
c1374bdb 883 def test_zone_comment_create(self):
6cc98ddf
CH
884 payload, zone = self.create_zone()
885 name = payload['name']
d708640f 886 rrset = {
6cc98ddf
CH
887 'changetype': 'replace',
888 'name': name,
889 'type': 'NS',
890 'comments': [
891 {
892 'account': 'test1',
893 'content': 'blah blah',
894 },
895 {
896 'account': 'test2',
897 'content': 'blah blah bleh',
898 }
899 ]
900 }
d708640f 901 payload = {'rrsets': [rrset]}
6cc98ddf 902 r = self.session.patch(
46d06a12 903 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
904 data=json.dumps(payload),
905 headers={'content-type': 'application/json'})
c1374bdb 906 self.assert_success_json(r)
6cc98ddf
CH
907 # make sure the comments have been set, and that the NS
908 # records are still present
46d06a12 909 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
6cc98ddf
CH
910 data = r.json()
911 print data
912 self.assertNotEquals([r for r in data['records'] if r['type'] == 'NS'], [])
913 self.assertNotEquals(data['comments'], [])
914 # verify that modified_at has been set by pdns
915 self.assertNotEquals([c for c in data['comments']][0]['modified_at'], 0)
916
c1374bdb 917 def test_zone_comment_delete(self):
6cc98ddf
CH
918 # Test: Delete ONLY comments.
919 payload, zone = self.create_zone()
920 name = payload['name']
d708640f 921 rrset = {
6cc98ddf
CH
922 'changetype': 'replace',
923 'name': name,
924 'type': 'NS',
925 'comments': []
926 }
d708640f 927 payload = {'rrsets': [rrset]}
6cc98ddf 928 r = self.session.patch(
46d06a12 929 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
930 data=json.dumps(payload),
931 headers={'content-type': 'application/json'})
c1374bdb 932 self.assert_success_json(r)
6cc98ddf 933 # make sure the NS records are still present
46d06a12 934 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
6cc98ddf
CH
935 data = r.json()
936 print data
937 self.assertNotEquals([r for r in data['records'] if r['type'] == 'NS'], [])
938 self.assertEquals(data['comments'], [])
939
c1374bdb 940 def test_zone_comment_stay_intact(self):
6cc98ddf
CH
941 # Test if comments on an rrset stay intact if the rrset is replaced
942 payload, zone = self.create_zone()
943 name = payload['name']
944 # create a comment
d708640f 945 rrset = {
6cc98ddf
CH
946 'changetype': 'replace',
947 'name': name,
948 'type': 'NS',
949 'comments': [
950 {
951 'account': 'test1',
952 'content': 'oh hi there',
2696eea0 953 'modified_at': 1111
6cc98ddf
CH
954 }
955 ]
956 }
d708640f 957 payload = {'rrsets': [rrset]}
6cc98ddf 958 r = self.session.patch(
46d06a12 959 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
960 data=json.dumps(payload),
961 headers={'content-type': 'application/json'})
c1374bdb 962 self.assert_success_json(r)
6cc98ddf 963 # replace rrset records
d708640f 964 rrset2 = {
6cc98ddf
CH
965 'changetype': 'replace',
966 'name': name,
967 'type': 'NS',
968 'records': [
969 {
970 "name": name,
971 "type": "NS",
6cc98ddf 972 "ttl": 3600,
1d6b70f9 973 "content": "ns1.bar.com.",
6cc98ddf
CH
974 "disabled": False
975 }
976 ]
977 }
d708640f 978 payload2 = {'rrsets': [rrset2]}
6cc98ddf 979 r = self.session.patch(
46d06a12 980 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
981 data=json.dumps(payload2),
982 headers={'content-type': 'application/json'})
c1374bdb 983 self.assert_success_json(r)
6cc98ddf 984 # make sure the comments still exist
46d06a12 985 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
6cc98ddf
CH
986 data = r.json()
987 print data
2696eea0
CH
988 # fix up input data for comparison with assertEquals.
989 # the fact that we're not sending name+type is part of the API spec.
990 for c in rrset['comments']:
991 c['name'] = rrset['name']
992 c['type'] = rrset['type']
993
d708640f
CH
994 self.assertEquals([r for r in data['records'] if r['type'] == 'NS'], rrset2['records'])
995 self.assertEquals(data['comments'], rrset['comments'])
6cc98ddf 996
c1374bdb 997 def test_zone_auto_ptr_ipv4(self):
1d6b70f9 998 revzone = '0.2.192.in-addr.arpa.'
d1587ceb
CH
999 self.create_zone(name=revzone)
1000 payload, zone = self.create_zone()
1001 name = payload['name']
1002 # replace with qname mismatch
d708640f 1003 rrset = {
d1587ceb
CH
1004 'changetype': 'replace',
1005 'name': name,
1006 'type': 'A',
1007 'records': [
1008 {
1009 "name": name,
1010 "type": "A",
d1587ceb
CH
1011 "ttl": 3600,
1012 "content": '192.2.0.2',
1013 "disabled": False,
1014 "set-ptr": True
1015 }
1016 ]
1017 }
d708640f 1018 payload = {'rrsets': [rrset]}
d1587ceb 1019 r = self.session.patch(
46d06a12 1020 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1021 data=json.dumps(payload),
1022 headers={'content-type': 'application/json'})
c1374bdb 1023 self.assert_success_json(r)
46d06a12 1024 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone))
d1587ceb
CH
1025 recs = r.json()['records']
1026 print recs
1027 revrec = [rec for rec in recs if rec['type'] == 'PTR']
1028 self.assertEquals(revrec, [{
1029 u'content': name,
1030 u'disabled': False,
1031 u'ttl': 3600,
d1587ceb 1032 u'type': u'PTR',
1d6b70f9 1033 u'name': u'2.0.2.192.in-addr.arpa.'
d1587ceb
CH
1034 }])
1035
c1374bdb 1036 def test_zone_auto_ptr_ipv6(self):
d1587ceb 1037 # 2001:DB8::bb:aa
1d6b70f9 1038 revzone = '8.b.d.0.1.0.0.2.ip6.arpa.'
d1587ceb
CH
1039 self.create_zone(name=revzone)
1040 payload, zone = self.create_zone()
1041 name = payload['name']
1042 # replace with qname mismatch
d708640f 1043 rrset = {
d1587ceb
CH
1044 'changetype': 'replace',
1045 'name': name,
1046 'type': 'AAAA',
1047 'records': [
1048 {
1049 "name": name,
1050 "type": "AAAA",
d1587ceb
CH
1051 "ttl": 3600,
1052 "content": '2001:DB8::bb:aa',
1053 "disabled": False,
1054 "set-ptr": True
1055 }
1056 ]
1057 }
d708640f 1058 payload = {'rrsets': [rrset]}
d1587ceb 1059 r = self.session.patch(
46d06a12 1060 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1061 data=json.dumps(payload),
1062 headers={'content-type': 'application/json'})
c1374bdb 1063 self.assert_success_json(r)
46d06a12 1064 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone))
d1587ceb
CH
1065 recs = r.json()['records']
1066 print recs
1067 revrec = [rec for rec in recs if rec['type'] == 'PTR']
1068 self.assertEquals(revrec, [{
1069 u'content': name,
1070 u'disabled': False,
1071 u'ttl': 3600,
d1587ceb 1072 u'type': u'PTR',
1d6b70f9 1073 u'name': u'a.a.0.0.b.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.'
d1587ceb
CH
1074 }])
1075
c1374bdb 1076 def test_search_rr_exact_zone(self):
b1902fab 1077 name = unique_zone_name()
1d6b70f9
CH
1078 self.create_zone(name=name, serial=22, soa_edit_api='')
1079 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.')))
c1374bdb 1080 self.assert_success_json(r)
b1902fab 1081 print r.json()
1d6b70f9
CH
1082 self.assertEquals(r.json(), [
1083 {u'object_type': u'zone', u'name': name, u'zone_id': name},
1084 {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600',
1085 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1086 u'ttl': 3600, u'type': u'SOA', u'name': name},
1087 {u'content': u'ns1.example.com.',
1088 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1089 u'ttl': 3600, u'type': u'NS', u'name': name},
1090 {u'content': u'ns2.example.com.',
1091 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1092 u'ttl': 3600, u'type': u'NS', u'name': name},
1093 ])
b1902fab 1094
c1374bdb 1095 def test_search_rr_substring(self):
1d6b70f9 1096 name = 'search-rr-zone.name.'
b1902fab 1097 self.create_zone(name=name)
46d06a12 1098 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-zone*"))
c1374bdb 1099 self.assert_success_json(r)
b1902fab
CH
1100 print r.json()
1101 # should return zone, SOA, ns1, ns2
60a8e825 1102 self.assertEquals(len(r.json()), 4)
b1902fab 1103
c1374bdb 1104 def test_search_rr_case_insensitive(self):
1d6b70f9 1105 name = 'search-rr-insenszone.name.'
57cb86d8 1106 self.create_zone(name=name)
46d06a12 1107 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-insensZONE*"))
c1374bdb 1108 self.assert_success_json(r)
57cb86d8
CH
1109 print r.json()
1110 # should return zone, SOA, ns1, ns2
60a8e825 1111 self.assertEquals(len(r.json()), 4)
57cb86d8 1112
02945d9a 1113
406497f5
CH
1114@unittest.skipIf(not is_auth(), "Not applicable")
1115class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
1116
1117 def setUp(self):
1118 super(AuthRootZone, self).setUp()
1119 # zone name is not unique, so delete the zone before each individual test.
46d06a12 1120 self.session.delete(self.url("/api/v1/servers/localhost/zones/=2E"))
406497f5
CH
1121
1122 def test_create_zone(self):
1d6b70f9 1123 payload, data = self.create_zone(name='.', serial=22, soa_edit_api='')
406497f5
CH
1124 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'):
1125 self.assertIn(k, data)
1126 if k in payload:
1127 self.assertEquals(data[k], payload[k])
1128 self.assertEquals(data['comments'], [])
1129 # validate generated SOA
1130 self.assertEquals(
1131 [r['content'] for r in data['records'] if r['type'] == 'SOA'][0],
1d6b70f9 1132 "a.misconfigured.powerdns.server. hostmaster. " + str(payload['serial']) +
406497f5
CH
1133 " 10800 3600 604800 3600"
1134 )
1135 # Regression test: verify zone list works
46d06a12 1136 zonelist = self.session.get(self.url("/api/v1/servers/localhost/zones")).json()
406497f5
CH
1137 print "zonelist:", zonelist
1138 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
1139 # Also test that fetching the zone works.
1140 print "id:", data['id']
1141 self.assertEquals(data['id'], '=2E')
46d06a12 1142 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json()
406497f5
CH
1143 print "zone (fetched):", data
1144 for k in ('name', 'kind'):
1145 self.assertIn(k, data)
1146 self.assertEquals(data[k], payload[k])
1d6b70f9 1147 self.assertEqual(data['records'][0]['name'], '.')
406497f5
CH
1148
1149 def test_update_zone(self):
1d6b70f9 1150 payload, zone = self.create_zone(name='.')
406497f5
CH
1151 zone_id = '=2E'
1152 # update, set as Master and enable SOA-EDIT-API
1153 payload = {
1154 'kind': 'Master',
1155 'masters': ['192.0.2.1', '192.0.2.2'],
1156 'soa_edit_api': 'EPOCH',
1157 'soa_edit': 'EPOCH'
1158 }
1159 r = self.session.put(
46d06a12 1160 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1161 data=json.dumps(payload),
1162 headers={'content-type': 'application/json'})
1163 self.assert_success_json(r)
1164 data = r.json()
1165 for k in payload.keys():
1166 self.assertIn(k, data)
1167 self.assertEquals(data[k], payload[k])
1168 # update, back to Native and empty(off)
1169 payload = {
1170 'kind': 'Native',
1171 'soa_edit_api': '',
1172 'soa_edit': ''
1173 }
1174 r = self.session.put(
46d06a12 1175 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1176 data=json.dumps(payload),
1177 headers={'content-type': 'application/json'})
1178 self.assert_success_json(r)
1179 data = r.json()
1180 for k in payload.keys():
1181 self.assertIn(k, data)
1182 self.assertEquals(data[k], payload[k])
1183
1184
c1374bdb 1185@unittest.skipIf(not is_recursor(), "Not applicable")
02945d9a
CH
1186class RecursorZones(ApiTestCase):
1187
37bc3d01
CH
1188 def create_zone(self, name=None, kind=None, rd=False, servers=None):
1189 if name is None:
1190 name = unique_zone_name()
1191 if servers is None:
1192 servers = []
02945d9a 1193 payload = {
37bc3d01
CH
1194 'name': name,
1195 'kind': kind,
1196 'servers': servers,
1197 'recursion_desired': rd
02945d9a
CH
1198 }
1199 r = self.session.post(
46d06a12 1200 self.url("/api/v1/servers/localhost/zones"),
02945d9a
CH
1201 data=json.dumps(payload),
1202 headers={'content-type': 'application/json'})
c1374bdb
CH
1203 self.assert_success_json(r)
1204 return payload, r.json()
37bc3d01 1205
c1374bdb 1206 def test_create_auth_zone(self):
37bc3d01 1207 payload, data = self.create_zone(kind='Native')
02945d9a
CH
1208 for k in payload.keys():
1209 self.assertEquals(data[k], payload[k])
1210
1d6b70f9
CH
1211 def test_create_zone_no_name(self):
1212 name = unique_zone_name()
1213 payload = {
1214 'name': '',
1215 'kind': 'Native',
1216 'servers': ['8.8.8.8'],
1217 'recursion_desired': False,
1218 }
1219 print payload
1220 r = self.session.post(
1221 self.url("/api/v1/servers/localhost/zones"),
1222 data=json.dumps(payload),
1223 headers={'content-type': 'application/json'})
1224 self.assertEquals(r.status_code, 422)
1225 self.assertIn('is not canonical', r.json()['error'])
1226
c1374bdb 1227 def test_create_forwarded_zone(self):
37bc3d01 1228 payload, data = self.create_zone(kind='Forwarded', rd=False, servers=['8.8.8.8'])
02945d9a
CH
1229 # return values are normalized
1230 payload['servers'][0] += ':53'
02945d9a
CH
1231 for k in payload.keys():
1232 self.assertEquals(data[k], payload[k])
1233
c1374bdb 1234 def test_create_forwarded_rd_zone(self):
1d6b70f9 1235 payload, data = self.create_zone(name='google.com.', kind='Forwarded', rd=True, servers=['8.8.8.8'])
02945d9a
CH
1236 # return values are normalized
1237 payload['servers'][0] += ':53'
02945d9a
CH
1238 for k in payload.keys():
1239 self.assertEquals(data[k], payload[k])
1240
c1374bdb 1241 def test_create_auth_zone_with_symbols(self):
37bc3d01 1242 payload, data = self.create_zone(name='foo/bar.'+unique_zone_name(), kind='Native')
1dbe38ba 1243 expected_id = (payload['name'].replace('/', '=2F'))
02945d9a
CH
1244 for k in payload.keys():
1245 self.assertEquals(data[k], payload[k])
1246 self.assertEquals(data['id'], expected_id)
e2367534 1247
c1374bdb 1248 def test_rename_auth_zone(self):
37bc3d01 1249 payload, data = self.create_zone(kind='Native')
1d6b70f9 1250 name = payload['name']
e2367534
CH
1251 # now rename it
1252 payload = {
1253 'name': 'renamed-'+name,
1254 'kind': 'Native',
1255 'recursion_desired': False
1256 }
1257 r = self.session.put(
46d06a12 1258 self.url("/api/v1/servers/localhost/zones/" + name),
e2367534
CH
1259 data=json.dumps(payload),
1260 headers={'content-type': 'application/json'})
c1374bdb 1261 self.assert_success_json(r)
e2367534
CH
1262 data = r.json()
1263 for k in payload.keys():
1264 self.assertEquals(data[k], payload[k])
37bc3d01 1265
37663c3b
CH
1266 def test_zone_delete(self):
1267 payload, zone = self.create_zone(kind='Native')
1268 name = payload['name']
46d06a12 1269 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
1270 self.assertEquals(r.status_code, 204)
1271 self.assertNotIn('Content-Type', r.headers)
1272
c1374bdb 1273 def test_search_rr_exact_zone(self):
1d6b70f9 1274 name = unique_zone_name()
37bc3d01 1275 self.create_zone(name=name, kind='Native')
46d06a12 1276 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name))
c1374bdb 1277 self.assert_success_json(r)
37bc3d01
CH
1278 print r.json()
1279 self.assertEquals(r.json(), [{u'type': u'zone', u'name': name, u'zone_id': name}])
1280
c1374bdb 1281 def test_search_rr_substring(self):
1d6b70f9 1282 name = 'search-rr-zone.name.'
37bc3d01 1283 self.create_zone(name=name, kind='Native')
46d06a12 1284 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=rr-zone"))
c1374bdb 1285 self.assert_success_json(r)
37bc3d01
CH
1286 print r.json()
1287 # should return zone, SOA
1288 self.assertEquals(len(r.json()), 2)
ccfabd0d
CH
1289
1290
1291@unittest.skipIf(not is_auth(), "Not applicable")
1292class AuthZoneKeys(ApiTestCase, AuthZonesHelperMixin):
1293
1294 def test_get_keys(self):
1295 r = self.session.get(
1296 self.url("/api/v1/servers/localhost/zones/powerdnssec.org./cryptokeys"))
1297 self.assert_success_json(r)
1298 keys = r.json()
1299 self.assertGreater(len(keys), 0)
1300
1301 key0 = deepcopy(keys[0])
1302 del key0['dnskey']
b6bd795c 1303 del key0['ds']
ccfabd0d
CH
1304 expected = {
1305 u'active': True,
1306 u'type': u'Cryptokey',
b6bd795c
PL
1307 u'keytype': u'csk',
1308 u'flags': 257,
ccfabd0d
CH
1309 u'id': 1}
1310 self.assertEquals(key0, expected)
1311
1312 keydata = keys[0]['dnskey'].split()
1313 self.assertEqual(len(keydata), 4)