}
}
+/** Throws ApiException if records with duplicate name/type/content are present.
+ * NOTE: sorts records in-place.
+ */
+static void checkDuplicateRecords(vector<DNSResourceRecord>& records) {
+ sort(records.begin(), records.end(),
+ [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
+ return rec_a.qname.toString() > rec_b.qname.toString() || \
+ rec_a.qtype.getCode() > rec_b.qtype.getCode() || \
+ rec_a.content < rec_b.content;
+ }
+ );
+ DNSResourceRecord previous;
+ for(const auto& rec : records) {
+ if (previous.qtype == rec.qtype && previous.qname == rec.qname && previous.content == rec.content) {
+ throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.getName() + " with content \"" + rec.content + "\"");
+ }
+ previous = rec;
+ }
+}
+
static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
DNSSECKeeper dk(&B);
auto document = req->json();
DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
apiCheckNameAllowedCharacters(zonename.toString());
+ zonename.makeUsLowerCase();
bool exists = B.getDomainInfo(zonename, di);
if(exists)
}
for(auto& rr : new_records) {
+ rr.qname.makeUsLowerCase();
if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": Name is out of zone");
apiCheckQNameAllowedCharacters(rr.qname.toString());
}
}
+ checkDuplicateRecords(new_records);
+
// no going back after this
if(!B.createDomain(zonename))
throw ApiException("Creating domain '"+zonename.toString()+"' failed");
rr.content = makeBackendRecordContent(rr.qtype, rr.content);
}
}
+ checkDuplicateRecords(new_records);
}
if (replace_comments) {
eq_zone_rrsets(data['rrsets'], expected)
- # noDot check
+ # check content in DB is stored WITHOUT trailing dot.
dbrecs = get_db_records(payload['name'], 'NS')
- self.assertEqual(dbrecs[0]['content'], 'powerdnssec2.ds9a.nl')
+ dbrec = next((dbrec for dbrec in dbrecs if dbrec['content'].startswith('powerdnssec1')))
+ self.assertEqual(dbrec['content'], 'powerdnssec1.ds9a.nl')
def test_import_zone_bind(self):
payload = {}
self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset1['records'])
self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset2['records'])
+ def test_zone_rr_update_duplicate_record(self):
+ name, payload, zone = self.create_zone()
+ rrset = {
+ 'changetype': 'replace',
+ 'name': name,
+ 'type': 'NS',
+ 'ttl': 3600,
+ 'records': [
+ {"content": "ns9999.example.com.", "disabled": False},
+ {"content": "ns9996.example.com.", "disabled": False},
+ {"content": "ns9987.example.com.", "disabled": False},
+ {"content": "ns9988.example.com.", "disabled": False},
+ {"content": "ns9999.example.com.", "disabled": False},
+ ]
+ }
+ payload = {'rrsets': [rrset]}
+ r = self.session.patch(
+ self.url("/api/v1/servers/localhost/zones/" + name),
+ data=json.dumps(payload),
+ headers={'content-type': 'application/json'})
+ self.assertEquals(r.status_code, 422)
+ self.assertIn('Duplicate record in RRset', r.json()['error'])
+
def test_zone_rr_delete(self):
name, payload, zone = self.create_zone()
# do a delete of all NS records (these are created with the zone)