Merge branch 'dk/1122-dnssec-rewrite' into dk/1091-dnssec

This commit is contained in:
David Kennedy 2023-10-11 16:28:12 -04:00
commit 67345fead4
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
5 changed files with 392 additions and 98 deletions

View file

@ -169,6 +169,7 @@ class DomainDsdataForm(forms.Form):
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
coerce=int, # need to coerce into int so dsData objects can be compared
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
error_messages={"required": ("Algorithm is required.")},
)
@ -176,6 +177,7 @@ class DomainDsdataForm(forms.Form):
digest_type = forms.TypedChoiceField(
required=True,
label="Digest Type",
coerce=int, # need to coerce into int so dsData objects can be compared
choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES, # type: ignore
error_messages={"required": ("Digest Type is required.")},
)
@ -201,6 +203,7 @@ class DomainKeydataForm(forms.Form):
flag = forms.TypedChoiceField(
required=True,
label="Flag",
coerce=int,
choices=FLAG_CHOICES,
error_messages={"required": ("Flag is required.")},
)
@ -208,6 +211,7 @@ class DomainKeydataForm(forms.Form):
protocol = forms.TypedChoiceField(
required=True,
label="Protocol",
coerce=int,
choices=PROTOCOL_CHOICES,
error_messages={"required": ("Protocol is required.")},
)
@ -215,6 +219,7 @@ class DomainKeydataForm(forms.Form):
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
coerce=int,
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
error_messages={"required": ("Algorithm is required.")},
)

View file

@ -2,6 +2,7 @@ from itertools import zip_longest
import logging
from datetime import date
from string import digits
from typing import Optional
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
from django.db import models
@ -283,7 +284,7 @@ class Domain(TimeStampedModel, DomainHelper):
return e.code
@Cache
def dnssecdata(self) -> extensions.DNSSECExtension:
def dnssecdata(self) -> Optional[extensions.DNSSECExtension]:
try:
return self._get_property("dnssecdata")
except Exception as err:
@ -292,21 +293,121 @@ class Domain(TimeStampedModel, DomainHelper):
logger.info("Domain does not have dnssec data defined %s" % err)
return None
def getDnssecdataChanges(
self, _dnssecdata: Optional[extensions.DNSSECExtension]
) -> tuple[dict, dict]:
"""
calls self.dnssecdata, it should pull from cache but may result
in an epp call
returns tuple of 2 values as follows:
addExtension: dict
remExtension: dict
addExtension includes all dsData or keyData to be added
remExtension includes all dsData or keyData to be removed
method operates on dsData OR keyData, never a mix of the two;
operates based on which is present in _dnssecdata;
if neither is present, addExtension will be empty dict, and
remExtension will be all existing dnssecdata to be deleted
"""
oldDnssecdata = self.dnssecdata
addDnssecdata: dict = {}
remDnssecdata: dict = {}
if _dnssecdata and _dnssecdata.dsData is not None:
# initialize addDnssecdata and remDnssecdata for dsData
addDnssecdata["dsData"] = _dnssecdata.dsData
if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
# if existing dsData not in new dsData, mark for removal
dsDataForRemoval = [
dsData
for dsData in oldDnssecdata.dsData
if dsData not in _dnssecdata.dsData
]
if len(dsDataForRemoval) > 0:
remDnssecdata["dsData"] = dsDataForRemoval
# if new dsData not in existing dsData, mark for add
dsDataForAdd = [
dsData
for dsData in _dnssecdata.dsData
if dsData not in oldDnssecdata.dsData
]
if len(dsDataForAdd) > 0:
addDnssecdata["dsData"] = dsDataForAdd
else:
addDnssecdata["dsData"] = None
elif _dnssecdata and _dnssecdata.keyData is not None:
# initialize addDnssecdata and remDnssecdata for keyData
addDnssecdata["keyData"] = _dnssecdata.keyData
if oldDnssecdata and len(oldDnssecdata.keyData) > 0:
# if existing keyData not in new keyData, mark for removal
keyDataForRemoval = [
keyData
for keyData in oldDnssecdata.keyData
if keyData not in _dnssecdata.keyData
]
if len(keyDataForRemoval) > 0:
remDnssecdata["keyData"] = keyDataForRemoval
# if new keyData not in existing keyData, mark for add
keyDataForAdd = [
keyData
for keyData in _dnssecdata.keyData
if keyData not in oldDnssecdata.keyData
]
if len(keyDataForAdd) > 0:
addDnssecdata["keyData"] = keyDataForAdd
else:
# there are no new dsData or keyData, remove all
remDnssecdata["dsData"] = getattr(oldDnssecdata, "dsData", None)
remDnssecdata["keyData"] = getattr(oldDnssecdata, "keyData", None)
return addDnssecdata, remDnssecdata
@dnssecdata.setter # type: ignore
def dnssecdata(self, _dnssecdata: dict):
updateParams = {
"maxSigLife": _dnssecdata.get("maxSigLife", None),
"dsData": _dnssecdata.get("dsData", None),
"keyData": _dnssecdata.get("keyData", None),
"remAllDsKeyData": True,
def dnssecdata(self, _dnssecdata: Optional[extensions.DNSSECExtension]):
_addDnssecdata, _remDnssecdata = self.getDnssecdataChanges(_dnssecdata)
addParams = {
"maxSigLife": _addDnssecdata.get("maxSigLife", None),
"dsData": _addDnssecdata.get("dsData", None),
"keyData": _addDnssecdata.get("keyData", None),
}
request = commands.UpdateDomain(name=self.name)
extension = commands.UpdateDomainDNSSECExtension(**updateParams)
request.add_extension(extension)
remParams = {
"maxSigLife": _remDnssecdata.get("maxSigLife", None),
"remDsData": _remDnssecdata.get("dsData", None),
"remKeyData": _remDnssecdata.get("keyData", None),
}
addRequest = commands.UpdateDomain(name=self.name)
addExtension = commands.UpdateDomainDNSSECExtension(**addParams)
addRequest.add_extension(addExtension)
remRequest = commands.UpdateDomain(name=self.name)
remExtension = commands.UpdateDomainDNSSECExtension(**remParams)
remRequest.add_extension(remExtension)
try:
registry.send(request, cleaned=True)
if (
"dsData" in _addDnssecdata
and _addDnssecdata["dsData"] is not None
or "keyData" in _addDnssecdata
and _addDnssecdata["keyData"] is not None
):
registry.send(addRequest, cleaned=True)
if (
"dsData" in _remDnssecdata
and _remDnssecdata["dsData"] is not None
or "keyData" in _remDnssecdata
and _remDnssecdata["keyData"] is not None
):
registry.send(remRequest, cleaned=True)
except RegistryError as e:
logger.error("Error adding DNSSEC, code was %s error was %s" % (e.code, e))
logger.error(
"Error updating DNSSEC, code was %s error was %s" % (e.code, e)
)
raise e
@nameservers.setter # type: ignore

View file

@ -7,7 +7,7 @@ import random
from string import ascii_uppercase
from django.test import TestCase
from unittest.mock import MagicMock, Mock, patch
from typing import List, Dict, Mapping, Any
from typing import List, Dict
from django.conf import settings
from django.contrib.auth import get_user_model, login
@ -704,19 +704,27 @@ class MockEppLib(TestCase):
"alg": 1,
"pubKey": "AQPJ////4Q==",
}
dnssecExtensionWithDsData: Mapping[Any, Any] = {
"dsData": [common.DSData(**addDsData1)] # type: ignore
}
dnssecExtensionWithMultDsData: Mapping[str, Any] = {
"dsData": [
common.DSData(**addDsData1), # type: ignore
common.DSData(**addDsData2), # type: ignore
],
}
dnssecExtensionWithKeyData: Mapping[str, Any] = {
"maxSigLife": 3215,
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
}
dnssecExtensionWithDsData = extensions.DNSSECExtension(
**{
"dsData": [
common.DSData(**addDsData1) # type: ignore
], # type: ignore
}
)
dnssecExtensionWithMultDsData = extensions.DNSSECExtension(
**{
"dsData": [
common.DSData(**addDsData1), # type: ignore
common.DSData(**addDsData2), # type: ignore
], # type: ignore
}
)
dnssecExtensionWithKeyData = extensions.DNSSECExtension(
**{
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
}
)
dnssecExtensionRemovingDsData = extensions.DNSSECExtension()
def mockSend(self, _request, cleaned):
"""Mocks the registry.send function used inside of domain.py
@ -758,23 +766,17 @@ class MockEppLib(TestCase):
elif getattr(_request, "name", None) == "dnssec-dsdata.gov":
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
],
extensions=[self.dnssecExtensionWithDsData],
)
elif getattr(_request, "name", None) == "dnssec-multdsdata.gov":
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
],
extensions=[self.dnssecExtensionWithMultDsData],
)
elif getattr(_request, "name", None) == "dnssec-keydata.gov":
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
],
extensions=[self.dnssecExtensionWithKeyData],
)
elif getattr(_request, "name", None) == "dnssec-none.gov":
# this case is not necessary, but helps improve readability

View file

@ -1035,15 +1035,27 @@ class TestRegistrantDNSSEC(MockEppLib):
"""Rule: Registrants may modify their secure DNS data"""
# helper function to create UpdateDomainDNSSECExtention object for verification
def createUpdateExtension(self, dnssecdata: extensions.DNSSECExtension):
return commands.UpdateDomainDNSSECExtension(
maxSigLife=dnssecdata.maxSigLife,
dsData=dnssecdata.dsData,
keyData=dnssecdata.keyData,
remDsData=None,
remKeyData=None,
remAllDsKeyData=True,
)
def createUpdateExtension(
self, dnssecdata: extensions.DNSSECExtension, remove=False
):
if not remove:
return commands.UpdateDomainDNSSECExtension(
maxSigLife=dnssecdata.maxSigLife,
dsData=dnssecdata.dsData,
keyData=dnssecdata.keyData,
remDsData=None,
remKeyData=None,
remAllDsKeyData=False,
)
else:
return commands.UpdateDomainDNSSECExtension(
maxSigLife=dnssecdata.maxSigLife,
dsData=None,
keyData=None,
remDsData=dnssecdata.dsData,
remKeyData=dnssecdata.keyData,
remAllDsKeyData=False,
)
def setUp(self):
"""
@ -1061,35 +1073,59 @@ class TestRegistrantDNSSEC(MockEppLib):
def test_user_adds_dnssec_data(self):
"""
Scenario: Registrant adds DNSSEC data.
Scenario: Registrant adds DNSSEC ds data.
Verify that both the setter and getter are functioning properly
This test verifies:
1 - setter calls UpdateDomain command
2 - setter adds the UpdateDNSSECExtension extension to the command
3 - setter causes the getter to call info domain on next get from cache
4 - getter properly parses dnssecdata from InfoDomain response and sets to cache
1 - setter initially calls InfoDomain command
2 - setter then calls UpdateDomain command
3 - setter adds the UpdateDNSSECExtension extension to the command
4 - setter causes the getter to call info domain on next get from cache
5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# need to use a separate patcher and side_effect for this test, as
# response from InfoDomain must be different for different iterations
# of the same command
def side_effect(_request, cleaned):
if isinstance(_request, commands.InfoDomain):
if mocked_send.call_count == 1:
return MagicMock(res_data=[self.mockDataInfoDomain])
else:
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[self.dnssecExtensionWithDsData],
)
else:
return MagicMock(res_data=[self.mockDataInfoHosts])
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
domain.dnssecdata = self.dnssecExtensionWithDsData
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
args, _ = self.mockedSendFunction.call_args
# assert that the extension matches
args, _ = mocked_send.call_args
# assert that the extension on the update matches
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
),
self.createUpdateExtension(self.dnssecExtensionWithDsData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
mocked_send.assert_has_calls(
[
call(
commands.InfoDomain(
name="dnssec-dsdata.gov",
),
cleaned=True,
),
call(
commands.UpdateDomain(
name="dnssec-dsdata.gov",
@ -1109,9 +1145,9 @@ class TestRegistrantDNSSEC(MockEppLib):
]
)
self.assertEquals(
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
)
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
patcher.stop()
def test_dnssec_is_idempotent(self):
"""
@ -1122,12 +1158,33 @@ class TestRegistrantDNSSEC(MockEppLib):
# registry normally sends in this case
This test verifies:
1 - UpdateDomain command called twice
2 - setter causes the getter to call info domain on next get from cache
3 - getter properly parses dnssecdata from InfoDomain response and sets to cache
1 - InfoDomain command is called first
2 - UpdateDomain command called on the initial setter
3 - setter causes the getter to call info domain on next get from cache
4 - UpdateDomain command is not called on second setter (no change)
5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
"""
# need to use a separate patcher and side_effect for this test, as
# response from InfoDomain must be different for different iterations
# of the same command
def side_effect(_request, cleaned):
if isinstance(_request, commands.InfoDomain):
if mocked_send.call_count == 1:
return MagicMock(res_data=[self.mockDataInfoDomain])
else:
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[self.dnssecExtensionWithDsData],
)
else:
return MagicMock(res_data=[self.mockDataInfoHosts])
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# set the dnssecdata once
@ -1136,15 +1193,11 @@ class TestRegistrantDNSSEC(MockEppLib):
domain.dnssecdata = self.dnssecExtensionWithDsData
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
mocked_send.assert_has_calls(
[
call(
commands.UpdateDomain(
commands.InfoDomain(
name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
auth_info=None,
),
cleaned=True,
),
@ -1164,12 +1217,18 @@ class TestRegistrantDNSSEC(MockEppLib):
),
cleaned=True,
),
call(
commands.InfoDomain(
name="dnssec-dsdata.gov",
),
cleaned=True,
),
]
)
self.assertEquals(
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
)
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
patcher.stop()
def test_user_adds_dnssec_data_multiple_dsdata(self):
"""
@ -1184,23 +1243,40 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
# need to use a separate patcher and side_effect for this test, as
# response from InfoDomain must be different for different iterations
# of the same command
def side_effect(_request, cleaned):
if isinstance(_request, commands.InfoDomain):
if mocked_send.call_count == 1:
return MagicMock(res_data=[self.mockDataInfoDomain])
else:
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[self.dnssecExtensionWithMultDsData],
)
else:
return MagicMock(res_data=[self.mockDataInfoHosts])
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
domain.dnssecdata = self.dnssecExtensionWithMultDsData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
args, _ = self.mockedSendFunction.call_args
args, _ = mocked_send.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
),
self.createUpdateExtension(self.dnssecExtensionWithMultDsData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
mocked_send.assert_has_calls(
[
call(
commands.UpdateDomain(
@ -1222,12 +1298,103 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData
)
patcher.stop()
def test_user_removes_dnssec_data(self):
"""
Scenario: Registrant removes DNSSEC ds data.
Verify that both the setter and getter are functioning properly
This test verifies:
1 - setter initially calls InfoDomain command
2 - first setter calls UpdateDomain command
3 - second setter calls InfoDomain command again
3 - setter then calls UpdateDomain command
4 - setter adds the UpdateDNSSECExtension extension to the command with rem
"""
# need to use a separate patcher and side_effect for this test, as
# response from InfoDomain must be different for different iterations
# of the same command
def side_effect(_request, cleaned):
if isinstance(_request, commands.InfoDomain):
if mocked_send.call_count == 1:
return MagicMock(res_data=[self.mockDataInfoDomain])
else:
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[self.dnssecExtensionWithDsData],
)
else:
return MagicMock(res_data=[self.mockDataInfoHosts])
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
# domain._invalidate_cache()
domain.dnssecdata = self.dnssecExtensionWithDsData
domain.dnssecdata = self.dnssecExtensionRemovingDsData
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
args, _ = mocked_send.call_args
# assert that the extension on the update matches
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
self.dnssecExtensionWithDsData,
remove=True,
),
)
mocked_send.assert_has_calls(
[
call(
commands.InfoDomain(
name="dnssec-dsdata.gov",
),
cleaned=True,
),
call(
commands.UpdateDomain(
name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
auth_info=None,
),
cleaned=True,
),
call(
commands.InfoDomain(
name="dnssec-dsdata.gov",
),
cleaned=True,
),
call(
commands.UpdateDomain(
name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
auth_info=None,
),
cleaned=True,
),
]
)
patcher.stop()
def test_user_adds_dnssec_keydata(self):
"""
Scenario: Registrant adds DNSSEC data.
Scenario: Registrant adds DNSSEC key data.
Verify that both the setter and getter are functioning properly
This test verifies:
@ -1238,23 +1405,40 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
# need to use a separate patcher and side_effect for this test, as
# response from InfoDomain must be different for different iterations
# of the same command
def side_effect(_request, cleaned):
if isinstance(_request, commands.InfoDomain):
if mocked_send.call_count == 1:
return MagicMock(res_data=[self.mockDataInfoDomain])
else:
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[self.dnssecExtensionWithKeyData],
)
else:
return MagicMock(res_data=[self.mockDataInfoHosts])
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
domain.dnssecdata = self.dnssecExtensionWithKeyData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
args, _ = self.mockedSendFunction.call_args
args, _ = mocked_send.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
),
self.createUpdateExtension(self.dnssecExtensionWithKeyData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
mocked_send.assert_has_calls(
[
call(
commands.UpdateDomain(
@ -1276,9 +1460,11 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData["keyData"]
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData.keyData
)
patcher.stop()
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the dns data is unsuccessful

View file

@ -378,8 +378,8 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
def form_valid(self, formset):
"""The formset is valid, perform something with it."""
# Set the nameservers from the formset
dnssecdata = {"dsData": []}
# Set the dnssecdata from the formset
dnssecdata = extensions.DNSSECExtension()
for form in formset:
try:
@ -387,17 +387,17 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
# or form.cleaned_data['delete'] == False:
dsrecord = {
"keyTag": form.cleaned_data["key_tag"],
"alg": form.cleaned_data["algorithm"],
"digestType": form.cleaned_data["digest_type"],
"alg": int(form.cleaned_data["algorithm"]),
"digestType": int(form.cleaned_data["digest_type"]),
"digest": form.cleaned_data["digest"],
}
dnssecdata["dsData"].append(common.DSData(**dsrecord))
if dnssecdata.dsData is None:
dnssecdata.dsData = []
dnssecdata.dsData.append(common.DSData(**dsrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
if len(dnssecdata["dsData"]) == 0:
dnssecdata = {}
try:
domain.dnssecdata = dnssecdata
except RegistryError as err:
@ -500,25 +500,25 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
"""The formset is valid, perform something with it."""
# Set the nameservers from the formset
dnssecdata = {"keyData": []}
dnssecdata = extensions.DNSSECExtension()
for form in formset:
try:
# if 'delete' not in form.cleaned_data
# or form.cleaned_data['delete'] == False:
keyrecord = {
"flags": form.cleaned_data["flag"],
"protocol": form.cleaned_data["protocol"],
"alg": form.cleaned_data["algorithm"],
"flags": int(form.cleaned_data["flag"]),
"protocol": int(form.cleaned_data["protocol"]),
"alg": int(form.cleaned_data["algorithm"]),
"pubKey": form.cleaned_data["pub_key"],
}
dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
if dnssecdata.keyData is None:
dnssecdata.keyData = []
dnssecdata.keyData.append(common.DNSSECKeyData(**keyrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
if len(dnssecdata["keyData"]) == 0:
dnssecdata = {}
try:
domain.dnssecdata = dnssecdata
except RegistryError as err: