]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
No more reasons to reject more than one EXTEND or PRUNE anymore. 16589/head
authorMiod Vallat <miod.vallat@powerdns.com>
Fri, 5 Dec 2025 12:57:27 +0000 (13:57 +0100)
committerMiod Vallat <miod.vallat@powerdns.com>
Fri, 5 Dec 2025 12:57:27 +0000 (13:57 +0100)
Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
pdns/ws-auth.cc
regression-tests.api/test_Zones.py

index 4b32cea4434fcb7674c8fc7730c03d9f933bc9d4..bb50fbc0137b90613e7c14c927aa992cc2bb8915 100644 (file)
@@ -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);
       }
index 509c29095a26862b0b630fbfb135153818c4d47f..1df56f5352ea2676422eca6d6b049e355ee841e2 100644 (file)
@@ -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.