Forráskód Böngészése

fix(api): require subname and type to be str in bulk RRset requests

Peter Thomassen 2 éve
szülő
commit
a4c357d987
2 módosított fájl, 22 hozzáadás és 3 törlés
  1. 12 3
      api/desecapi/serializers.py
  2. 10 0
      api/desecapi/tests/test_rrsets_bulk.py

+ 12 - 3
api/desecapi/serializers.py

@@ -270,7 +270,7 @@ class RRsetListSerializer(serializers.ListSerializer):
                 self.fail('empty')
 
         ret = []
-        errors = []
+        errors = [{} for _ in data]
         partial = self.partial
 
         # build look-up objects for instances and data, so we can look them up with their keys
@@ -284,6 +284,12 @@ class RRsetListSerializer(serializers.ListSerializer):
             if not isinstance(item, dict):
                 self.fail('invalid', datatype=type(item).__name__)
             s, t = self._key(item)  # subname, type
+            if not (isinstance(s, str) or s is None):
+                errors[idx].update(subname=f"Expected a string, but got {type(s).__name__}.")
+            if not (isinstance(t, str) or t is None):
+                errors[idx].update(type=f"Expected a string, but got {type(t).__name__}.")
+            if errors[idx]:
+                continue
             # Construct an index of the RRsets in `data` by `s` and `t`. As (subname, type) may be given multiple times
             # (although invalid), we make indices[s][t] a set to properly keep track. We also check and record RRsets
             # which are known in the database (once per subname), using index `None` (for checking CNAME exclusivity).
@@ -295,12 +301,16 @@ class RRsetListSerializer(serializers.ListSerializer):
 
         collapsed_indices = copy.deepcopy(indices)
         for idx, item in enumerate(data):
+            if errors[idx]:
+                continue
             if item.get('records') == []:
                 s, t = self._key(item)
                 collapsed_indices[s][t] -= {idx, None}
 
         # Iterate over all rows in the data given
         for idx, item in enumerate(data):
+            if errors[idx]:
+                continue
             try:
                 # see if other rows have the same key
                 s, t = self._key(item)
@@ -337,10 +347,9 @@ class RRsetListSerializer(serializers.ListSerializer):
                 # with partial value and instance in place, let the validation begin!
                 validated = self.child.run_validation(item)
             except serializers.ValidationError as exc:
-                errors.append(exc.detail)
+                errors[idx].update(exc.detail)
             else:
                 ret.append(validated)
-                errors.append({})
 
         self.partial = partial
 

+ 10 - 0
api/desecapi/tests/test_rrsets_bulk.py

@@ -390,6 +390,16 @@ class AuthenticatedRRSetBulkTestCase(AuthenticatedRRSetBaseTestCase):
         response = self.client.bulk_put_rr_sets(domain_name=self.my_empty_domain.name, payload=[42])
         self.assertContains(response, 'Expected a dictionary, but got int.', status_code=status.HTTP_400_BAD_REQUEST)
 
+    def test_bulk_put_does_not_accept_rrsets_with_nonstr_subname(self):
+        payload = [{"subname": ["foobar"], "type": "A", "ttl": 3600, "records": ["1.2.3.4"]}]
+        response = self.client.bulk_put_rr_sets(domain_name=self.my_empty_domain.name, payload=payload)
+        self.assertContains(response, 'Expected a string, but got list.', status_code=status.HTTP_400_BAD_REQUEST)
+
+    def test_bulk_put_does_not_accept_rrsets_with_nonstr_type(self):
+        payload = [{"subname": "foobar", "type": ["A"], "ttl": 3600, "records": ["1.2.3.4"]}]
+        response = self.client.bulk_put_rr_sets(domain_name=self.my_empty_domain.name, payload=payload)
+        self.assertContains(response, 'Expected a string, but got list.', status_code=status.HTTP_400_BAD_REQUEST)
+
     def test_bulk_put_full(self):
         # Full PUT always works
         with self.assertPdnsRequests(self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)):