From: Miod Vallat Date: Fri, 5 Dec 2025 12:57:27 +0000 (+0100) Subject: No more reasons to reject more than one EXTEND or PRUNE anymore. X-Git-Tag: rec-5.4.0-alpha1~21^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=64252645422fd2876d10ac318b3ae7ffea0a875a;p=thirdparty%2Fpdns.git No more reasons to reject more than one EXTEND or PRUNE anymore. Signed-off-by: Miod Vallat --- diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 4b32cea443..bb50fbc013 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -2681,8 +2681,13 @@ static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInf key currentKey{qname, qtype}; if (auto iter = changes.find(currentKey); iter != changes.end()) { auto operations = iter->second; - if ((operations & newOperation) != 0) { - throw ApiException("Duplicate RRset " + qname.toString() + " IN " + qtype.toString() + " with changetype: " + changetype); + // Only allow one DELETE or REPLACE operation per RRset. On the other + // hand, it makes sense to allow multiple PRUNE or EXTEND, since the + // individual records they'll concern might differ. + if (operationType == DELETE || operationType == REPLACE) { + if ((operations & newOperation) != 0) { + throw ApiException("Duplicate RRset " + qname.toString() + " IN " + qtype.toString() + " with changetype: " + changetype); + } } changes.insert_or_assign(currentKey, operations | newOperation); } diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index 509c29095a..1df56f5352 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -1347,39 +1347,6 @@ $NAME$ 1D IN SOA ns1.example.org. hostmaster.example.org. ( self.assertEqual(r.status_code, 422) self.assert_in_json_error("Exactly one record should be provided", r.json()) - def test_zone_rr_bogus_update_4(self): - name, payload, zone = self.create_zone() - # duplicate rrset in extend - rrset1 = { - 'changetype': 'extend', - 'name': 'a.'+name, - 'type': 'A', - 'ttl': 3600, - 'records': [ - { - "content": "127.0.0.1", - "disabled": False - } - ] - } - payload = {'rrsets': [rrset1, rrset1]} - r = self.session.patch( - self.url("/api/v1/servers/localhost/zones/" + name), - data=json.dumps(payload), - headers={'content-type': 'application/json'}) - self.assertEqual(r.status_code, 422) - self.assert_in_json_error("Duplicate RRset", r.json()) - # duplicate rrset in extend/prune - rrset2 = rrset1 - rrset2['changetype'] = 'prune' - payload = {'rrsets': [rrset1, rrset2]} - r = self.session.patch( - self.url("/api/v1/servers/localhost/zones/" + name), - data=json.dumps(payload), - headers={'content-type': 'application/json'}) - self.assertEqual(r.status_code, 422) - self.assert_in_json_error("Duplicate RRset", r.json()) - def test_zone_rr_update(self): name, payload, zone = self.create_zone() # do a replace (= update) @@ -1804,13 +1771,13 @@ $NAME$ 1D IN SOA ns1.example.org. hostmaster.example.org. ( 'ttl': 3600, 'records': [] } - # complete rrset + # second half of the rrset rrset3 = { 'changetype': 'replace', 'name': 'a.'+name, 'type': 'A', 'ttl': 3600, - 'records': [ a1, a2, a3, a4 ] + 'records': [ a3, a4 ] } # timid record deletion rrset4 = { @@ -1820,7 +1787,22 @@ $NAME$ 1D IN SOA ns1.example.org. hostmaster.example.org. ( 'ttl': 3600, 'records': [ a3 ] } - payload = {'rrsets': [rrset1, rrset2, rrset3, rrset4]} + # first half of the rrset + rrset5 = { + 'changetype': 'extend', + 'name': 'a.'+name, + 'type': 'A', + 'ttl': 3600, + 'records': [ a1 ] + } + rrset6 = { + 'changetype': 'extend', + 'name': 'a.'+name, + 'type': 'A', + 'ttl': 3600, + 'records': [ a2 ] + } + payload = {'rrsets': [rrset1, rrset2, rrset3, rrset4, rrset5, rrset6]} r = self.session.patch( self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload), @@ -1828,7 +1810,12 @@ $NAME$ 1D IN SOA ns1.example.org. hostmaster.example.org. ( self.assert_success(r) # verify zone contents data = self.get_zone(name) - self.assertEqual(get_rrset(data, 'a.' + name, 'A')['records'], [ a1, a2, a4]) + # note that we can't assume anything about the order of the records + records = get_rrset(data, 'a.' + name, 'A')['records'] + self.assertEqual(len(records), 3) + self.assertTrue(a1 in records) + self.assertTrue(a2 in records) + self.assertTrue(a4 in records) def test_zone_disable_reenable(self): # This also tests that SOA-EDIT-API works.