From 406497f50ca776c26db3df4a56bf560095c4db32 Mon Sep 17 00:00:00 2001 From: Christian Hofstaedtler Date: Tue, 24 Feb 2015 23:46:27 +0100 Subject: [PATCH] API (Auth): fix hosting of root zone As discovered by @jpmens in #2216, the API could not create the root zone, and listing zones would also fail when the root zone was present. This corrects those bugs, plus another that prevented reading the root zone, and adds a small API test set for the root zone. Fixes #2216. --- pdns/ws-api.cc | 6 +-- pdns/ws-auth.cc | 13 ++--- regression-tests.api/test_Zones.py | 80 ++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc index 41389f3664..a42f1a6fb9 100644 --- a/pdns/ws-api.cc +++ b/pdns/ws-api.cc @@ -262,7 +262,7 @@ string apiZoneIdToName(const string& id) { zonename = ss.str(); // strip trailing dot - if (zonename.substr(zonename.size()-1) == ".") { + if (zonename.size() > 0 && zonename.substr(zonename.size()-1) == ".") { zonename.resize(zonename.size()-1); } return zonename; @@ -285,14 +285,14 @@ string apiZoneNameToId(const string& name) { string id = ss.str(); // add trailing dot - if (id.substr(id.size()-1) != ".") { + if (id.size() == 0 || id.substr(id.size()-1) != ".") { id += "."; } // special handling for the root zone, as a dot on it's own doesn't work // everywhere. if (id == ".") { - id = (boost::format("=%02x") % (int)('.')).str(); + id = (boost::format("=%02X") % (int)('.')).str(); } return id; } diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 5214619dc2..7b35a91b64 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -601,18 +601,15 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) { Document document; req->json(document); string zonename = stringFromJson(document, "name"); - string dotsuffix = "." + zonename; - string zonestring = stringFromJson(document, "zone", ""); - - // TODO: better validation of zonename - if(zonename.empty()) - throw ApiException("Zone name empty"); - // strip any trailing dots - while (zonename.substr(zonename.size()-1) == ".") { + // strip trailing dot (from spec PoV this is wrong, but be nice to clients) + if (zonename.size() > 0 && zonename.substr(zonename.size()-1) == ".") { zonename.resize(zonename.size()-1); } + string dotsuffix = "." + zonename; + string zonestring = stringFromJson(document, "zone", ""); + bool exists = B.getDomainInfo(zonename, di); if(exists) throw ApiException("Domain '"+zonename+"' already exists"); diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index a035c875d1..a894589b4b 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -22,9 +22,7 @@ class Zones(ApiTestCase): self.assertIn(field, example_com) -@unittest.skipIf(not is_auth(), "Not applicable") -class AuthZones(ApiTestCase): - +class AuthZonesHelperMixin(object): def create_zone(self, name=None, **kwargs): if name is None: name = unique_zone_name() @@ -47,6 +45,10 @@ class AuthZones(ApiTestCase): self.assertEquals(r.status_code, 201) return payload, r.json() + +@unittest.skipIf(not is_auth(), "Not applicable") +class AuthZones(ApiTestCase, AuthZonesHelperMixin): + def test_create_zone(self): payload, data = self.create_zone(serial=22) for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'): @@ -1004,6 +1006,78 @@ fred IN A 192.168.0.4 self.assertEquals(len(r.json()), 1) # FIXME test disarmed for now (should be 4) +@unittest.skipIf(not is_auth(), "Not applicable") +class AuthRootZone(ApiTestCase, AuthZonesHelperMixin): + + def setUp(self): + super(AuthRootZone, self).setUp() + # zone name is not unique, so delete the zone before each individual test. + self.session.delete(self.url("/servers/localhost/zones/=2E")) + + def test_create_zone(self): + payload, data = self.create_zone(name='', serial=22) + for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'): + self.assertIn(k, data) + if k in payload: + self.assertEquals(data[k], payload[k]) + self.assertEquals(data['comments'], []) + # validate generated SOA + self.assertEquals( + [r['content'] for r in data['records'] if r['type'] == 'SOA'][0], + "a.misconfigured.powerdns.server hostmaster." + payload['name'] + " " + str(payload['serial']) + + " 10800 3600 604800 3600" + ) + # Regression test: verify zone list works + zonelist = self.session.get(self.url("/servers/localhost/zones")).json() + print "zonelist:", zonelist + self.assertIn(payload['name'], [zone['name'] for zone in zonelist]) + # Also test that fetching the zone works. + print "id:", data['id'] + self.assertEquals(data['id'], '=2E') + data = self.session.get(self.url("/servers/localhost/zones/" + data['id'])).json() + print "zone (fetched):", data + for k in ('name', 'kind'): + self.assertIn(k, data) + self.assertEquals(data[k], payload[k]) + self.assertEqual(data['records'][0]['name'], '') + + def test_update_zone(self): + payload, zone = self.create_zone(name='') + name = '' + zone_id = '=2E' + # update, set as Master and enable SOA-EDIT-API + payload = { + 'kind': 'Master', + 'masters': ['192.0.2.1', '192.0.2.2'], + 'soa_edit_api': 'EPOCH', + 'soa_edit': 'EPOCH' + } + r = self.session.put( + self.url("/servers/localhost/zones/" + zone_id), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assert_success_json(r) + data = r.json() + for k in payload.keys(): + self.assertIn(k, data) + self.assertEquals(data[k], payload[k]) + # update, back to Native and empty(off) + payload = { + 'kind': 'Native', + 'soa_edit_api': '', + 'soa_edit': '' + } + r = self.session.put( + self.url("/servers/localhost/zones/" + zone_id), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assert_success_json(r) + data = r.json() + for k in payload.keys(): + self.assertIn(k, data) + self.assertEquals(data[k], payload[k]) + + @unittest.skipIf(not is_recursor(), "Not applicable") class RecursorZones(ApiTestCase): -- 2.47.2