mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-09-02 07:15:24 +00:00
committed by
Paul Cleary
parent
5040d07c00
commit
77c4536e37
@@ -64,6 +64,82 @@ def test_create_recordset_with_dns_verify(shared_zone_test_context):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_naptr_origin_record(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test creating naptr origin records works
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
result_rs = None
|
||||||
|
try:
|
||||||
|
new_rs = {
|
||||||
|
'zoneId': shared_zone_test_context.ok_zone['id'],
|
||||||
|
'name': 'ok.',
|
||||||
|
'type': 'NAPTR',
|
||||||
|
'ttl': 100,
|
||||||
|
'records': [
|
||||||
|
{
|
||||||
|
'order': 10,
|
||||||
|
'preference': 100,
|
||||||
|
'flags': 'S',
|
||||||
|
'service': 'SIP+D2T',
|
||||||
|
'regexp': '',
|
||||||
|
'replacement': '_sip._udp.ok.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
result = client.create_recordset(new_rs, status=202)
|
||||||
|
|
||||||
|
assert_that(result['changeType'], is_('Create'))
|
||||||
|
assert_that(result['status'], is_('Pending'))
|
||||||
|
assert_that(result['created'], is_not(none()))
|
||||||
|
assert_that(result['userId'], is_not(none()))
|
||||||
|
|
||||||
|
result_rs = result['recordSet']
|
||||||
|
result_rs = client.wait_until_recordset_change_status(result, 'Complete')['recordSet']
|
||||||
|
verify_recordset(result_rs, new_rs)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if result_rs:
|
||||||
|
delete_result = client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202)
|
||||||
|
|
||||||
|
def test_create_naptr_non_origin_record(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test creating naptr records works
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
result_rs = None
|
||||||
|
try:
|
||||||
|
new_rs = {
|
||||||
|
'zoneId': shared_zone_test_context.ok_zone['id'],
|
||||||
|
'name': 'testnaptr',
|
||||||
|
'type': 'NAPTR',
|
||||||
|
'ttl': 100,
|
||||||
|
'records': [
|
||||||
|
{
|
||||||
|
'order': 10,
|
||||||
|
'preference': 100,
|
||||||
|
'flags': 'S',
|
||||||
|
'service': 'SIP+D2T',
|
||||||
|
'regexp': '',
|
||||||
|
'replacement': '_sip._udp.ok.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
result = client.create_recordset(new_rs, status=202)
|
||||||
|
|
||||||
|
assert_that(result['changeType'], is_('Create'))
|
||||||
|
assert_that(result['status'], is_('Pending'))
|
||||||
|
assert_that(result['created'], is_not(none()))
|
||||||
|
assert_that(result['userId'], is_not(none()))
|
||||||
|
|
||||||
|
result_rs = result['recordSet']
|
||||||
|
result_rs = client.wait_until_recordset_change_status(result, 'Complete')['recordSet']
|
||||||
|
verify_recordset(result_rs, new_rs)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if result_rs:
|
||||||
|
delete_result = client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202)
|
||||||
|
|
||||||
def test_create_srv_recordset_with_service_and_protocol(shared_zone_test_context):
|
def test_create_srv_recordset_with_service_and_protocol(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
Test creating a new srv record set with service and protocol works
|
Test creating a new srv record set with service and protocol works
|
||||||
|
@@ -133,6 +133,7 @@ trait DnsConversions {
|
|||||||
case DNS.Type.SOA => RecordType.SOA
|
case DNS.Type.SOA => RecordType.SOA
|
||||||
case DNS.Type.SPF => RecordType.SPF
|
case DNS.Type.SPF => RecordType.SPF
|
||||||
case DNS.Type.SRV => RecordType.SRV
|
case DNS.Type.SRV => RecordType.SRV
|
||||||
|
case DNS.Type.NAPTR => RecordType.NAPTR
|
||||||
case DNS.Type.SSHFP => RecordType.SSHFP
|
case DNS.Type.SSHFP => RecordType.SSHFP
|
||||||
case DNS.Type.TXT => RecordType.TXT
|
case DNS.Type.TXT => RecordType.TXT
|
||||||
case _ => RecordType.UNKNOWN
|
case _ => RecordType.UNKNOWN
|
||||||
@@ -150,6 +151,7 @@ trait DnsConversions {
|
|||||||
case x: DNS.SOARecord => fromSOARecord(x, zoneName, zoneId)
|
case x: DNS.SOARecord => fromSOARecord(x, zoneName, zoneId)
|
||||||
case x: DNS.SPFRecord => fromSPFRecord(x, zoneName, zoneId)
|
case x: DNS.SPFRecord => fromSPFRecord(x, zoneName, zoneId)
|
||||||
case x: DNS.SRVRecord => fromSRVRecord(x, zoneName, zoneId)
|
case x: DNS.SRVRecord => fromSRVRecord(x, zoneName, zoneId)
|
||||||
|
case x: DNS.NAPTRRecord => fromNAPTRRecord(x, zoneName, zoneId)
|
||||||
case x: DNS.SSHFPRecord => fromSSHFPRecord(x, zoneName, zoneId)
|
case x: DNS.SSHFPRecord => fromSSHFPRecord(x, zoneName, zoneId)
|
||||||
case x: DNS.TXTRecord => fromTXTRecord(x, zoneName, zoneId)
|
case x: DNS.TXTRecord => fromTXTRecord(x, zoneName, zoneId)
|
||||||
case _ => fromUnknownRecordType(r, zoneName, zoneId)
|
case _ => fromUnknownRecordType(r, zoneName, zoneId)
|
||||||
@@ -267,6 +269,18 @@ trait DnsConversions {
|
|||||||
List(SRVData(data.getPriority, data.getWeight, data.getPort, data.getTarget.toString))
|
List(SRVData(data.getPriority, data.getWeight, data.getPort, data.getTarget.toString))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def fromNAPTRRecord(r: DNS.NAPTRRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
||||||
|
fromDnsRecord(r, zoneName, zoneId) { data =>
|
||||||
|
List(
|
||||||
|
NAPTRData(
|
||||||
|
data.getOrder,
|
||||||
|
data.getPreference,
|
||||||
|
data.getFlags,
|
||||||
|
data.getService,
|
||||||
|
data.getRegexp,
|
||||||
|
data.getReplacement.toString))
|
||||||
|
}
|
||||||
|
|
||||||
def fromSSHFPRecord(r: DNS.SSHFPRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
def fromSSHFPRecord(r: DNS.SSHFPRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
||||||
fromDnsRecord(r, zoneName, zoneId) { data =>
|
fromDnsRecord(r, zoneName, zoneId) { data =>
|
||||||
List(SSHFPData(data.getAlgorithm, data.getDigestType, new String(data.getFingerPrint)))
|
List(SSHFPData(data.getAlgorithm, data.getDigestType, new String(data.getFingerPrint)))
|
||||||
@@ -339,6 +353,18 @@ trait DnsConversions {
|
|||||||
port,
|
port,
|
||||||
DNS.Name.fromString(target))
|
DNS.Name.fromString(target))
|
||||||
|
|
||||||
|
case NAPTRData(order, preference, flags, service, regexp, replacement) =>
|
||||||
|
new DNS.NAPTRRecord(
|
||||||
|
recordName,
|
||||||
|
DNS.DClass.IN,
|
||||||
|
ttl,
|
||||||
|
order,
|
||||||
|
preference,
|
||||||
|
flags,
|
||||||
|
service,
|
||||||
|
regexp,
|
||||||
|
DNS.Name.fromString(replacement))
|
||||||
|
|
||||||
case SSHFPData(algorithm, typ, fingerprint) =>
|
case SSHFPData(algorithm, typ, fingerprint) =>
|
||||||
new DNS.SSHFPRecord(recordName, DNS.DClass.IN, ttl, algorithm, typ, fingerprint.getBytes)
|
new DNS.SSHFPRecord(recordName, DNS.DClass.IN, ttl, algorithm, typ, fingerprint.getBytes)
|
||||||
|
|
||||||
@@ -371,6 +397,7 @@ trait DnsConversions {
|
|||||||
case RecordType.SPF => DNS.Type.SPF
|
case RecordType.SPF => DNS.Type.SPF
|
||||||
case RecordType.SSHFP => DNS.Type.SSHFP
|
case RecordType.SSHFP => DNS.Type.SSHFP
|
||||||
case RecordType.SRV => DNS.Type.SRV
|
case RecordType.SRV => DNS.Type.SRV
|
||||||
|
case RecordType.NAPTR => DNS.Type.NAPTR
|
||||||
case RecordType.TXT => DNS.Type.TXT
|
case RecordType.TXT => DNS.Type.TXT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -92,7 +92,7 @@ object RecordSetValidations {
|
|||||||
case NS => nsValidations(newRecordSet, zone)
|
case NS => nsValidations(newRecordSet, zone)
|
||||||
case SOA => soaValidations(newRecordSet, zone)
|
case SOA => soaValidations(newRecordSet, zone)
|
||||||
case PTR => ptrValidations(newRecordSet, zone)
|
case PTR => ptrValidations(newRecordSet, zone)
|
||||||
case SRV | TXT => ().asRight // SRV and TXT do not go through dotted host check
|
case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check
|
||||||
case DS => dsValidations(newRecordSet, existingRecordsWithName, zone)
|
case DS => dsValidations(newRecordSet, existingRecordsWithName, zone)
|
||||||
case _ => isNotDotted(newRecordSet, zone)
|
case _ => isNotDotted(newRecordSet, zone)
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ object RecordSetValidations {
|
|||||||
case NS => nsValidations(newRecordSet, zone, Some(oldRecordSet))
|
case NS => nsValidations(newRecordSet, zone, Some(oldRecordSet))
|
||||||
case SOA => soaValidations(newRecordSet, zone)
|
case SOA => soaValidations(newRecordSet, zone)
|
||||||
case PTR => ptrValidations(newRecordSet, zone)
|
case PTR => ptrValidations(newRecordSet, zone)
|
||||||
case SRV | TXT => ().asRight // SRV and TXT do not go through dotted host check
|
case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check
|
||||||
case DS => dsValidations(newRecordSet, existingRecordsWithName, zone)
|
case DS => dsValidations(newRecordSet, existingRecordsWithName, zone)
|
||||||
case _ => isNotDotted(newRecordSet, zone)
|
case _ => isNotDotted(newRecordSet, zone)
|
||||||
}
|
}
|
||||||
|
@@ -106,7 +106,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
|||||||
changesWithUserIds
|
changesWithUserIds
|
||||||
.filter { chg =>
|
.filter { chg =>
|
||||||
chg.recordSet.name != zone.name && chg.recordSet.name.contains(".") &&
|
chg.recordSet.name != zone.name && chg.recordSet.name.contains(".") &&
|
||||||
chg.recordSet.typ != RecordType.SRV && chg.recordSet.typ != RecordType.TXT
|
chg.recordSet.typ != RecordType.SRV && chg.recordSet.typ != RecordType.TXT &&
|
||||||
|
chg.recordSet.typ != RecordType.NAPTR
|
||||||
}
|
}
|
||||||
.map(_.recordSet.name)
|
.map(_.recordSet.name)
|
||||||
.grouped(1000)
|
.grouped(1000)
|
||||||
|
@@ -57,6 +57,7 @@ trait DnsJsonProtocol extends JsonValidation {
|
|||||||
SOASerializer,
|
SOASerializer,
|
||||||
SPFSerializer,
|
SPFSerializer,
|
||||||
SRVSerializer,
|
SRVSerializer,
|
||||||
|
NAPTRSerializer,
|
||||||
SSHFPSerializer,
|
SSHFPSerializer,
|
||||||
TXTSerializer,
|
TXTSerializer,
|
||||||
JsonV[ZoneACL]
|
JsonV[ZoneACL]
|
||||||
@@ -263,6 +264,7 @@ trait DnsJsonProtocol extends JsonValidation {
|
|||||||
case RecordType.SOA => js.required[List[SOAData]]("Missing SOA Records")
|
case RecordType.SOA => js.required[List[SOAData]]("Missing SOA Records")
|
||||||
case RecordType.SPF => js.required[List[SPFData]]("Missing SPF Records")
|
case RecordType.SPF => js.required[List[SPFData]]("Missing SPF Records")
|
||||||
case RecordType.SRV => js.required[List[SRVData]]("Missing SRV Records")
|
case RecordType.SRV => js.required[List[SRVData]]("Missing SRV Records")
|
||||||
|
case RecordType.NAPTR => js.required[List[NAPTRData]]("Missing NAPTR Records")
|
||||||
case RecordType.SSHFP => js.required[List[SSHFPData]]("Missing SSHFP Records")
|
case RecordType.SSHFP => js.required[List[SSHFPData]]("Missing SSHFP Records")
|
||||||
case RecordType.TXT => js.required[List[TXTData]]("Missing TXT Records")
|
case RecordType.TXT => js.required[List[TXTData]]("Missing TXT Records")
|
||||||
case _ => s"Unsupported type $typ, valid types include ${RecordType.values}".invalidNel
|
case _ => s"Unsupported type $typ, valid types include ${RecordType.values}".invalidNel
|
||||||
@@ -417,6 +419,44 @@ trait DnsJsonProtocol extends JsonValidation {
|
|||||||
).mapN(SRVData.apply)
|
).mapN(SRVData.apply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case object NAPTRSerializer extends ValidationSerializer[NAPTRData] {
|
||||||
|
override def fromJson(js: JValue): ValidatedNel[String, NAPTRData] =
|
||||||
|
(
|
||||||
|
(js \ "order")
|
||||||
|
.required[Integer]("Missing NAPTR.order")
|
||||||
|
.check(
|
||||||
|
"NAPTR.order must be an unsigned 16 bit number" -> (i => i <= 65535 && i >= 0)
|
||||||
|
),
|
||||||
|
(js \ "preference")
|
||||||
|
.required[Integer]("Missing NAPTR.preference")
|
||||||
|
.check(
|
||||||
|
"NAPTR.preference must be an unsigned 16 bit number" -> (i => i <= 65535 && i >= 0)
|
||||||
|
),
|
||||||
|
(js \ "flags")
|
||||||
|
.required[String]("Missing NAPTR.flags")
|
||||||
|
.check(
|
||||||
|
"NAPTR.flags must be less than 2 characters" -> (_.length < 2)
|
||||||
|
),
|
||||||
|
(js \ "service")
|
||||||
|
.required[String]("Missing NAPTR.service")
|
||||||
|
.check(
|
||||||
|
"NAPTR.service must be less than 255 characters" -> checkDomainNameLen
|
||||||
|
),
|
||||||
|
(js \ "regexp")
|
||||||
|
.required[String]("Missing NAPTR.regexp")
|
||||||
|
.check(
|
||||||
|
"NAPTR.regexp must be less than 255 characters" -> checkDomainNameLen
|
||||||
|
),
|
||||||
|
// should also check regex validity
|
||||||
|
(js \ "replacement")
|
||||||
|
.required[String]("Missing NAPTR.replacement")
|
||||||
|
.check(
|
||||||
|
"NAPTR.replacement must be less than 255 characters" -> checkDomainNameLen
|
||||||
|
)
|
||||||
|
.map(ensureTrailingDot)
|
||||||
|
).mapN(NAPTRData.apply)
|
||||||
|
}
|
||||||
|
|
||||||
case object SSHFPSerializer extends ValidationSerializer[SSHFPData] {
|
case object SSHFPSerializer extends ValidationSerializer[SSHFPData] {
|
||||||
override def fromJson(js: JValue): ValidatedNel[String, SSHFPData] =
|
override def fromJson(js: JValue): ValidatedNel[String, SSHFPData] =
|
||||||
(
|
(
|
||||||
|
@@ -142,6 +142,16 @@ class DnsConversionsSpec
|
|||||||
DateTime.now,
|
DateTime.now,
|
||||||
None,
|
None,
|
||||||
List(SRVData(1, 2, 3, "target.vinyldns.")))
|
List(SRVData(1, 2, 3, "target.vinyldns.")))
|
||||||
|
private val testNAPTR = RecordSet(
|
||||||
|
testZone.id,
|
||||||
|
"naptr-record",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
200,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(NAPTRData(1, 2, "U", "E2U+sip", "!.*!test.!", "target.vinyldns."))
|
||||||
|
)
|
||||||
private val testSSHFP = RecordSet(
|
private val testSSHFP = RecordSet(
|
||||||
testZone.id,
|
testZone.id,
|
||||||
"sshfp-record",
|
"sshfp-record",
|
||||||
@@ -350,6 +360,11 @@ class DnsConversionsSpec
|
|||||||
verifyMatch(result, testSRV)
|
verifyMatch(result, testSRV)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"convert NAPTR record set" in {
|
||||||
|
val result = rightValue(toDnsRRset(testNAPTR, testZoneName))
|
||||||
|
verifyMatch(result, testNAPTR)
|
||||||
|
}
|
||||||
|
|
||||||
"convert TXT record set" in {
|
"convert TXT record set" in {
|
||||||
val result = rightValue(toDnsRRset(testTXT, testZoneName))
|
val result = rightValue(toDnsRRset(testTXT, testZoneName))
|
||||||
verifyMatch(result, testTXT)
|
verifyMatch(result, testTXT)
|
||||||
@@ -464,6 +479,9 @@ class DnsConversionsSpec
|
|||||||
"convert to/from RecordType SRV" in {
|
"convert to/from RecordType SRV" in {
|
||||||
verifyMatch(testSRV, roundTrip(testSRV))
|
verifyMatch(testSRV, roundTrip(testSRV))
|
||||||
}
|
}
|
||||||
|
"convert to/from RecordType NAPTR" in {
|
||||||
|
verifyMatch(testNAPTR, roundTrip(testNAPTR))
|
||||||
|
}
|
||||||
"convert to/from RecordType SSHFP" in {
|
"convert to/from RecordType SSHFP" in {
|
||||||
verifyMatch(testSSHFP, roundTrip(testSSHFP))
|
verifyMatch(testSSHFP, roundTrip(testSSHFP))
|
||||||
}
|
}
|
||||||
@@ -509,6 +527,9 @@ class DnsConversionsSpec
|
|||||||
"support SRV" in {
|
"support SRV" in {
|
||||||
toDnsRecordType(RecordType.SRV) shouldBe DNS.Type.SRV
|
toDnsRecordType(RecordType.SRV) shouldBe DNS.Type.SRV
|
||||||
}
|
}
|
||||||
|
"support NAPTR" in {
|
||||||
|
toDnsRecordType(RecordType.NAPTR) shouldBe DNS.Type.NAPTR
|
||||||
|
}
|
||||||
"support TXT" in {
|
"support TXT" in {
|
||||||
toDnsRecordType(RecordType.TXT) shouldBe DNS.Type.TXT
|
toDnsRecordType(RecordType.TXT) shouldBe DNS.Type.TXT
|
||||||
}
|
}
|
||||||
|
@@ -55,11 +55,21 @@ class RecordSetValidationsSpec
|
|||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"return invalid request when adding a NAPTR record to an IP4 reverse zone" in {
|
||||||
|
val error = leftValue(validRecordTypes(naptr, zoneIp4))
|
||||||
|
error shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
"return invalid request when adding a SRV record to an IP6 reverse zone" in {
|
"return invalid request when adding a SRV record to an IP6 reverse zone" in {
|
||||||
val error = leftValue(validRecordTypes(srv, zoneIp6))
|
val error = leftValue(validRecordTypes(srv, zoneIp6))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"return invalid request when adding a NAPTR record to an IP6 reverse zone" in {
|
||||||
|
val error = leftValue(validRecordTypes(naptr, zoneIp6))
|
||||||
|
error shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
"return ok when adding an acceptable record to a forward zone" in {
|
"return ok when adding an acceptable record to a forward zone" in {
|
||||||
validRecordTypes(aaaa, okZone) should be(right)
|
validRecordTypes(aaaa, okZone) should be(right)
|
||||||
}
|
}
|
||||||
@@ -214,6 +224,29 @@ class RecordSetValidationsSpec
|
|||||||
typeSpecificAddValidations(test, List(), zone) should be(right)
|
typeSpecificAddValidations(test, List(), zone) should be(right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"Skip dotted checks on NAPTR" should {
|
||||||
|
"return success for an NAPTR record with FQDN" in {
|
||||||
|
val test = naptr.copy(name = "sub.naptr.example.com.")
|
||||||
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
|
typeSpecificAddValidations(test, List(), zone) should be(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return success for an NAPTR record without FQDN" in {
|
||||||
|
val test = naptr.copy(name = "sub.naptr")
|
||||||
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
|
typeSpecificAddValidations(test, List(), zone) should be(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return success on a wildcard NAPTR" in {
|
||||||
|
val test = naptr.copy(name = "*.sub.naptr.example.com.")
|
||||||
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
|
typeSpecificAddValidations(test, List(), zone) should be(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
"Skip dotted checks on PTR" should {
|
"Skip dotted checks on PTR" should {
|
||||||
"return success for a PTR record with dots in a reverse zone" in {
|
"return success for a PTR record with dots in a reverse zone" in {
|
||||||
val test = ptrIp4.copy(name = "10.1.2.")
|
val test = ptrIp4.copy(name = "10.1.2.")
|
||||||
|
@@ -154,6 +154,15 @@ class ProtobufConversionsSpec
|
|||||||
DateTime.now,
|
DateTime.now,
|
||||||
None,
|
None,
|
||||||
List(SRVData(1, 2, 3, "target")))
|
List(SRVData(1, 2, 3, "target")))
|
||||||
|
private val naptr = RecordSet(
|
||||||
|
zone.id,
|
||||||
|
"naptr",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
200,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(NAPTRData(1, 2, "U", "E2U+sip", "!.*!test.!", "target")))
|
||||||
private val sshfp = RecordSet(
|
private val sshfp = RecordSet(
|
||||||
zone.id,
|
zone.id,
|
||||||
"sshfp",
|
"sshfp",
|
||||||
@@ -527,6 +536,10 @@ class ProtobufConversionsSpec
|
|||||||
fromPB(toPB(srv)) shouldBe srv
|
fromPB(toPB(srv)) shouldBe srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"convert from protobuf for NAPTR recordset" in {
|
||||||
|
fromPB(toPB(naptr)) shouldBe naptr
|
||||||
|
}
|
||||||
|
|
||||||
"convert from protobuf for SSHFP recordset" in {
|
"convert from protobuf for SSHFP recordset" in {
|
||||||
fromPB(toPB(sshfp)) shouldBe sshfp
|
fromPB(toPB(sshfp)) shouldBe sshfp
|
||||||
}
|
}
|
||||||
@@ -665,6 +678,25 @@ class ProtobufConversionsSpec
|
|||||||
data shouldBe from
|
data shouldBe from
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"convert to protobuf for NAPTR data" in {
|
||||||
|
val from = NAPTRData(1, 2, "U", "E2U+sip", "!.*!test.!", "target")
|
||||||
|
val pb = toPB(from)
|
||||||
|
pb.getOrder shouldBe from.order
|
||||||
|
pb.getPreference shouldBe from.preference
|
||||||
|
pb.getFlags shouldBe from.flags
|
||||||
|
pb.getService shouldBe from.service
|
||||||
|
pb.getRegexp shouldBe from.regexp
|
||||||
|
pb.getReplacement shouldBe from.replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
"convert from protobuf for NAPTR data" in {
|
||||||
|
val from = NAPTRData(1, 2, "U", "E2U+sip", "!.*!test.!", "target")
|
||||||
|
val pb = toPB(from)
|
||||||
|
val data = fromPB(pb)
|
||||||
|
|
||||||
|
data shouldBe from
|
||||||
|
}
|
||||||
|
|
||||||
"convert to protobuf for SSHFP data" in {
|
"convert to protobuf for SSHFP data" in {
|
||||||
val from = SSHFPData(1, 2, "fingerprint")
|
val from = SSHFPData(1, 2, "fingerprint")
|
||||||
val pb = toPB(from)
|
val pb = toPB(from)
|
||||||
|
@@ -261,6 +261,16 @@ class RecordSetRoutingSpec
|
|||||||
None,
|
None,
|
||||||
List(SRVData(1, 2, 3, "target.")))
|
List(SRVData(1, 2, 3, "target.")))
|
||||||
|
|
||||||
|
private val naptr = RecordSet(
|
||||||
|
okZone.id,
|
||||||
|
"naptr",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
200,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(NAPTRData(1, 2, "U", "E2U+sip", "!.*!test.!", "target.")))
|
||||||
|
|
||||||
private val sshfp = RecordSet(
|
private val sshfp = RecordSet(
|
||||||
okZone.id,
|
okZone.id,
|
||||||
"sshfp",
|
"sshfp",
|
||||||
@@ -325,6 +335,7 @@ class RecordSetRoutingSpec
|
|||||||
soa.id -> soa,
|
soa.id -> soa,
|
||||||
spf.id -> spf,
|
spf.id -> spf,
|
||||||
srv.id -> srv,
|
srv.id -> srv,
|
||||||
|
naptr.id -> naptr,
|
||||||
sshfp.id -> sshfp,
|
sshfp.id -> sshfp,
|
||||||
txt.id -> txt
|
txt.id -> txt
|
||||||
)
|
)
|
||||||
@@ -908,6 +919,7 @@ class RecordSetRoutingSpec
|
|||||||
validateErrors(testRecordBase(RecordType.SOA, JNothing), "Missing SOA Records")
|
validateErrors(testRecordBase(RecordType.SOA, JNothing), "Missing SOA Records")
|
||||||
validateErrors(testRecordBase(RecordType.SPF, JNothing), "Missing SPF Records")
|
validateErrors(testRecordBase(RecordType.SPF, JNothing), "Missing SPF Records")
|
||||||
validateErrors(testRecordBase(RecordType.SRV, JNothing), "Missing SRV Records")
|
validateErrors(testRecordBase(RecordType.SRV, JNothing), "Missing SRV Records")
|
||||||
|
validateErrors(testRecordBase(RecordType.NAPTR, JNothing), "Missing NAPTR Records")
|
||||||
validateErrors(testRecordBase(RecordType.SSHFP, JNothing), "Missing SSHFP Records")
|
validateErrors(testRecordBase(RecordType.SSHFP, JNothing), "Missing SSHFP Records")
|
||||||
validateErrors(testRecordBase(RecordType.TXT, JNothing), "Missing TXT Records")
|
validateErrors(testRecordBase(RecordType.TXT, JNothing), "Missing TXT Records")
|
||||||
}
|
}
|
||||||
@@ -1124,6 +1136,56 @@ class RecordSetRoutingSpec
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"supports NAPTR" in {
|
||||||
|
validateCreateRecordType(naptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return errors for NAPTR record missing data" in {
|
||||||
|
validateErrors(
|
||||||
|
testRecordType(RecordType.NAPTR, "key" -> "val"),
|
||||||
|
"Missing NAPTR.order",
|
||||||
|
"Missing NAPTR.preference",
|
||||||
|
"Missing NAPTR.flags",
|
||||||
|
"Missing NAPTR.service",
|
||||||
|
"Missing NAPTR.regexp",
|
||||||
|
"Missing NAPTR.replacement"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return errors for invalid NAPTR record data" in {
|
||||||
|
validateErrors(
|
||||||
|
testRecordType(
|
||||||
|
RecordType.NAPTR,
|
||||||
|
("replacement" -> Random.alphanumeric.take(260).mkString) ~~
|
||||||
|
// should check regex better
|
||||||
|
("regexp" -> Random.alphanumeric.take(260).mkString) ~~
|
||||||
|
("service" -> Random.alphanumeric.take(260).mkString) ~~
|
||||||
|
("flags" -> Random.alphanumeric.take(2).mkString) ~~
|
||||||
|
("order" -> 50000000) ~~
|
||||||
|
("preference" -> 50000000)
|
||||||
|
),
|
||||||
|
"NAPTR.order must be an unsigned 16 bit number",
|
||||||
|
"NAPTR.preference must be an unsigned 16 bit number",
|
||||||
|
"NAPTR.flags must be less than 2 characters",
|
||||||
|
"NAPTR.service must be less than 255 characters",
|
||||||
|
"NAPTR.regexp must be less than 255 characters",
|
||||||
|
"NAPTR.replacement must be less than 255 characters"
|
||||||
|
)
|
||||||
|
validateErrors(
|
||||||
|
testRecordType(
|
||||||
|
RecordType.NAPTR,
|
||||||
|
("regexp" -> Random.alphanumeric.take(10).mkString) ~~
|
||||||
|
("service" -> Random.alphanumeric.take(10).mkString) ~~
|
||||||
|
("replacement" -> Random.alphanumeric.take(10).mkString) ~~
|
||||||
|
("flags" -> Random.alphanumeric.take(1).mkString) ~~
|
||||||
|
("order" -> -1) ~~
|
||||||
|
("preference" -> -1)
|
||||||
|
),
|
||||||
|
"NAPTR.order must be an unsigned 16 bit number",
|
||||||
|
"NAPTR.preference must be an unsigned 16 bit number"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
"supports SSHFP" in {
|
"supports SSHFP" in {
|
||||||
validateCreateRecordType(sshfp)
|
validateCreateRecordType(sshfp)
|
||||||
}
|
}
|
||||||
|
@@ -444,6 +444,55 @@ class VinylDNSJsonProtocolSpec
|
|||||||
anonymize(actual).records shouldBe List(SRVData(1, 20, 5000, "srv."))
|
anonymize(actual).records shouldBe List(SRVData(1, 20, 5000, "srv."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"parse a record set with an absolute NAPTR target passes" in {
|
||||||
|
val recordSetJValue: JValue =
|
||||||
|
("zoneId" -> "1") ~~
|
||||||
|
("name" -> "TestRecordName") ~~
|
||||||
|
("type" -> "NAPTR") ~~
|
||||||
|
("ttl" -> 1000) ~~
|
||||||
|
("status" -> "Pending") ~~
|
||||||
|
("records" -> Extraction.decompose(
|
||||||
|
Set(NAPTRData(1, 20, "U", "E2U+sip", "!.*!test.!", "naptr."))))
|
||||||
|
|
||||||
|
val expected = RecordSet(
|
||||||
|
"1",
|
||||||
|
"TestRecordName",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
1000,
|
||||||
|
RecordSetStatus.Pending,
|
||||||
|
new DateTime(2010, 1, 1, 0, 0),
|
||||||
|
records = List(NAPTRData(1, 20, "U", "E2U+sip", "!.*!test.!", "naptr."))
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = recordSetJValue.extract[RecordSet]
|
||||||
|
anonymize(actual) shouldBe anonymize(expected)
|
||||||
|
}
|
||||||
|
"convert relative NAPTR target to an absolute NAPTR target" in {
|
||||||
|
val recordSetJValue: JValue =
|
||||||
|
("zoneId" -> "1") ~~
|
||||||
|
("name" -> "TestRecordName") ~~
|
||||||
|
("type" -> "NAPTR") ~~
|
||||||
|
("ttl" -> 1000) ~~
|
||||||
|
("status" -> "Pending") ~~
|
||||||
|
("records" -> Extraction.decompose(
|
||||||
|
Set(NAPTRData(1, 20, "U", "E2U+sip", "!.*!test.!", "naptr"))))
|
||||||
|
|
||||||
|
val expected = RecordSet(
|
||||||
|
"1",
|
||||||
|
"TestRecordName",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
1000,
|
||||||
|
RecordSetStatus.Pending,
|
||||||
|
new DateTime(2010, 1, 1, 0, 0),
|
||||||
|
records = List(NAPTRData(1, 20, "U", "E2U+sip", "!.*!test.!", "naptr."))
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = recordSetJValue.extract[RecordSet]
|
||||||
|
anonymize(actual) shouldBe anonymize(expected)
|
||||||
|
anonymize(actual).records shouldBe List(
|
||||||
|
NAPTRData(1, 20, "U", "E2U+sip", "!.*!test.!", "naptr."))
|
||||||
|
}
|
||||||
|
|
||||||
"parse a record set with an absolute PTR domain name" in {
|
"parse a record set with an absolute PTR domain name" in {
|
||||||
val recordSetJValue: JValue =
|
val recordSetJValue: JValue =
|
||||||
("zoneId" -> "1") ~~
|
("zoneId" -> "1") ~~
|
||||||
|
@@ -94,6 +94,15 @@ message SRVData {
|
|||||||
required string target = 4;
|
required string target = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message NAPTRData {
|
||||||
|
required int32 order = 1;
|
||||||
|
required int32 preference = 2;
|
||||||
|
required string flags = 4;
|
||||||
|
required string service = 5;
|
||||||
|
required string regexp = 6;
|
||||||
|
required string replacement = 7;
|
||||||
|
}
|
||||||
|
|
||||||
message SSHFPData {
|
message SSHFPData {
|
||||||
required int32 algorithm = 1;
|
required int32 algorithm = 1;
|
||||||
required int32 typ = 2;
|
required int32 typ = 2;
|
||||||
|
@@ -73,6 +73,26 @@ object SRVData {
|
|||||||
new SRVData(priority, weight, port, ensureTrailingDot(target))
|
new SRVData(priority, weight, port, ensureTrailingDot(target))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final case class NAPTRData(
|
||||||
|
order: Integer,
|
||||||
|
preference: Integer,
|
||||||
|
flags: String,
|
||||||
|
service: String,
|
||||||
|
regexp: String,
|
||||||
|
replacement: String)
|
||||||
|
extends RecordData
|
||||||
|
|
||||||
|
object NAPTRData {
|
||||||
|
def apply(
|
||||||
|
order: Integer,
|
||||||
|
preference: Integer,
|
||||||
|
flags: String,
|
||||||
|
service: String,
|
||||||
|
regexp: String,
|
||||||
|
replacement: String): NAPTRData =
|
||||||
|
new NAPTRData(order, preference, flags, service, regexp, ensureTrailingDot(replacement))
|
||||||
|
}
|
||||||
|
|
||||||
final case class SSHFPData(algorithm: Integer, typ: Integer, fingerprint: String) extends RecordData
|
final case class SSHFPData(algorithm: Integer, typ: Integer, fingerprint: String) extends RecordData
|
||||||
|
|
||||||
final case class TXTData(text: String) extends RecordData
|
final case class TXTData(text: String) extends RecordData
|
||||||
|
@@ -22,7 +22,7 @@ import org.joda.time.DateTime
|
|||||||
|
|
||||||
object RecordType extends Enumeration {
|
object RecordType extends Enumeration {
|
||||||
type RecordType = Value
|
type RecordType = Value
|
||||||
val A, AAAA, CNAME, DS, PTR, MX, NS, SOA, SRV, TXT, SSHFP, SPF, UNKNOWN = Value
|
val A, AAAA, CNAME, DS, PTR, MX, NS, SOA, SRV, NAPTR, TXT, SSHFP, SPF, UNKNOWN = Value
|
||||||
}
|
}
|
||||||
|
|
||||||
object RecordSetStatus extends Enumeration {
|
object RecordSetStatus extends Enumeration {
|
||||||
|
@@ -165,6 +165,7 @@ trait ProtobufConversions {
|
|||||||
case RecordType.SOA => fromPB(VinylDNSProto.SOAData.parseFrom(rd.getData))
|
case RecordType.SOA => fromPB(VinylDNSProto.SOAData.parseFrom(rd.getData))
|
||||||
case RecordType.SPF => fromPB(VinylDNSProto.SPFData.parseFrom(rd.getData))
|
case RecordType.SPF => fromPB(VinylDNSProto.SPFData.parseFrom(rd.getData))
|
||||||
case RecordType.SRV => fromPB(VinylDNSProto.SRVData.parseFrom(rd.getData))
|
case RecordType.SRV => fromPB(VinylDNSProto.SRVData.parseFrom(rd.getData))
|
||||||
|
case RecordType.NAPTR => fromPB(VinylDNSProto.NAPTRData.parseFrom(rd.getData))
|
||||||
case RecordType.SSHFP => fromPB(VinylDNSProto.SSHFPData.parseFrom(rd.getData))
|
case RecordType.SSHFP => fromPB(VinylDNSProto.SSHFPData.parseFrom(rd.getData))
|
||||||
case RecordType.TXT => fromPB(VinylDNSProto.TXTData.parseFrom(rd.getData))
|
case RecordType.TXT => fromPB(VinylDNSProto.TXTData.parseFrom(rd.getData))
|
||||||
}
|
}
|
||||||
@@ -203,6 +204,15 @@ trait ProtobufConversions {
|
|||||||
def fromPB(data: VinylDNSProto.SRVData): SRVData =
|
def fromPB(data: VinylDNSProto.SRVData): SRVData =
|
||||||
SRVData(data.getPriority, data.getWeight, data.getPort, data.getTarget)
|
SRVData(data.getPriority, data.getWeight, data.getPort, data.getTarget)
|
||||||
|
|
||||||
|
def fromPB(data: VinylDNSProto.NAPTRData): NAPTRData =
|
||||||
|
NAPTRData(
|
||||||
|
data.getOrder,
|
||||||
|
data.getPreference,
|
||||||
|
data.getFlags,
|
||||||
|
data.getService,
|
||||||
|
data.getRegexp,
|
||||||
|
data.getReplacement)
|
||||||
|
|
||||||
def fromPB(data: VinylDNSProto.SSHFPData): SSHFPData =
|
def fromPB(data: VinylDNSProto.SSHFPData): SSHFPData =
|
||||||
SSHFPData(data.getAlgorithm, data.getTyp, data.getFingerPrint)
|
SSHFPData(data.getAlgorithm, data.getTyp, data.getFingerPrint)
|
||||||
|
|
||||||
@@ -283,6 +293,17 @@ trait ProtobufConversions {
|
|||||||
.setWeight(data.weight)
|
.setWeight(data.weight)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
def toPB(data: NAPTRData): VinylDNSProto.NAPTRData =
|
||||||
|
VinylDNSProto.NAPTRData
|
||||||
|
.newBuilder()
|
||||||
|
.setOrder(data.order)
|
||||||
|
.setPreference(data.preference)
|
||||||
|
.setFlags(data.flags)
|
||||||
|
.setService(data.service)
|
||||||
|
.setRegexp(data.regexp)
|
||||||
|
.setReplacement(data.replacement)
|
||||||
|
.build()
|
||||||
|
|
||||||
def toPB(data: SSHFPData): VinylDNSProto.SSHFPData =
|
def toPB(data: SSHFPData): VinylDNSProto.SSHFPData =
|
||||||
VinylDNSProto.SSHFPData
|
VinylDNSProto.SSHFPData
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
@@ -307,6 +328,7 @@ trait ProtobufConversions {
|
|||||||
case x: SOAData => toPB(x)
|
case x: SOAData => toPB(x)
|
||||||
case x: SPFData => toPB(x)
|
case x: SPFData => toPB(x)
|
||||||
case x: SRVData => toPB(x)
|
case x: SRVData => toPB(x)
|
||||||
|
case x: NAPTRData => toPB(x)
|
||||||
case x: SSHFPData => toPB(x)
|
case x: SSHFPData => toPB(x)
|
||||||
case x: TXTData => toPB(x)
|
case x: TXTData => toPB(x)
|
||||||
}
|
}
|
||||||
|
@@ -108,6 +108,16 @@ object TestRecordSetData {
|
|||||||
None,
|
None,
|
||||||
List(SRVData(1, 2, 3, "target")))
|
List(SRVData(1, 2, 3, "target")))
|
||||||
|
|
||||||
|
val naptr: RecordSet = RecordSet(
|
||||||
|
okZone.id,
|
||||||
|
"naptr",
|
||||||
|
RecordType.NAPTR,
|
||||||
|
200,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(NAPTRData(1, 2, "S", "E2U+sip", "", "target")))
|
||||||
|
|
||||||
val mx: RecordSet = RecordSet(
|
val mx: RecordSet = RecordSet(
|
||||||
okZone.id,
|
okZone.id,
|
||||||
"mx",
|
"mx",
|
||||||
|
@@ -63,5 +63,11 @@ class RecordSetSpec extends WordSpec with Matchers {
|
|||||||
|
|
||||||
result.records shouldBe List(SRVData(1, 2, 3, "target."))
|
result.records shouldBe List(SRVData(1, 2, 3, "target."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"ensure trailing dot on NAPTR record target" in {
|
||||||
|
val result = naptr
|
||||||
|
|
||||||
|
result.records shouldBe List(NAPTRData(1, 2, "S", "E2U+sip", "", "target."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -318,6 +318,7 @@ object MySqlRecordSetRepository extends ProtobufConversions {
|
|||||||
case RecordType.TXT => 10
|
case RecordType.TXT => 10
|
||||||
case RecordType.SOA => 11
|
case RecordType.SOA => 11
|
||||||
case RecordType.DS => 12
|
case RecordType.DS => 12
|
||||||
|
case RecordType.NAPTR => 13
|
||||||
case RecordType.UNKNOWN => unknownRecordType
|
case RecordType.UNKNOWN => unknownRecordType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -265,6 +265,21 @@
|
|||||||
</span>
|
</span>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
<span ng-if="record.type == 'NAPTR'">
|
||||||
|
<ul class="table-cell-list">
|
||||||
|
<li ng-repeat="item in record.naptrItems track by $index"
|
||||||
|
ng-if="!record.onlyFour || $index <= 3">
|
||||||
|
Order: {{ item.order }} | Preference: {{ item.preference }} | Flags: {{ item.flags }}
|
||||||
|
| Service: {{ item.service }} | Regexp: {{ item.regexp }} | Replacement: {{ item.replacement }}
|
||||||
|
</li>
|
||||||
|
<li ng-if="record.onlyFour && record.naptrItems.length > 4">
|
||||||
|
<a href ng-click="record.onlyFour=false">more...</a>
|
||||||
|
</li>
|
||||||
|
<li ng-if="!record.onlyFour">
|
||||||
|
<a href ng-click="record.onlyFour=true">less...</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
<span ng-if="record.type == 'SSHFP'">
|
<span ng-if="record.type == 'SSHFP'">
|
||||||
<ul class="table-cell-list">
|
<ul class="table-cell-list">
|
||||||
<li ng-repeat="item in record.sshfpItems track by $index"
|
<li ng-repeat="item in record.sshfpItems track by $index"
|
||||||
|
@@ -63,7 +63,7 @@ angular.module('controller.manageZones', [])
|
|||||||
readOnly: false
|
readOnly: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$scope.aclRecordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', 'SPF', 'SRV', 'SSHFP', 'TXT'];
|
$scope.aclRecordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', 'SPF', 'SRV', 'NAPTR', 'SSHFP', 'TXT'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zone modal control functions
|
* Zone modal control functions
|
||||||
|
@@ -24,7 +24,7 @@ angular.module('controller.records', [])
|
|||||||
$scope.query = "";
|
$scope.query = "";
|
||||||
$scope.alerts = [];
|
$scope.alerts = [];
|
||||||
|
|
||||||
$scope.recordTypes = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SPF', 'SRV', 'SSHFP', 'TXT'];
|
$scope.recordTypes = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SPF', 'SRV', 'NAPTR', 'SSHFP', 'TXT'];
|
||||||
$scope.sshfpAlgorithms = [{name: '(1) RSA', number: 1}, {name: '(2) DSA', number: 2}, {name: '(3) ECDSA', number: 3},
|
$scope.sshfpAlgorithms = [{name: '(1) RSA', number: 1}, {name: '(2) DSA', number: 2}, {name: '(3) ECDSA', number: 3},
|
||||||
{name: '(4) Ed25519', number: 4}];
|
{name: '(4) Ed25519', number: 4}];
|
||||||
$scope.sshfpTypes = [{name: '(1) SHA-1', number: 1}, {name: '(2) SHA-256', number: 2}];
|
$scope.sshfpTypes = [{name: '(1) SHA-1', number: 1}, {name: '(2) SHA-256', number: 2}];
|
||||||
@@ -92,6 +92,7 @@ angular.module('controller.records', [])
|
|||||||
ttl: 300,
|
ttl: 300,
|
||||||
mxItems: [{preference:'', exchange:''}],
|
mxItems: [{preference:'', exchange:''}],
|
||||||
srvItems: [{priority:'', weight:'', port:'', target:''}],
|
srvItems: [{priority:'', weight:'', port:'', target:''}],
|
||||||
|
naptrItems: [{order:'', preference:'', flags:'', service:'', regexp:'', replacement:''}],
|
||||||
sshfpItems: [{algorithm:'', type:'', fingerprint:''}]
|
sshfpItems: [{algorithm:'', type:'', fingerprint:''}]
|
||||||
};
|
};
|
||||||
$scope.currentRecord = angular.copy(record);
|
$scope.currentRecord = angular.copy(record);
|
||||||
@@ -239,6 +240,18 @@ angular.module('controller.records', [])
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.addNewNaptr = function() {
|
||||||
|
var dataObj = {order:'', preference:'', flags:'', service:'', regexp:'', replacement:''};
|
||||||
|
$scope.currentRecord.naptrItems.push(dataObj);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteNaptr = function(index) {
|
||||||
|
$scope.currentRecord.naptrItems.splice(index, 1);
|
||||||
|
if($scope.currentRecord.naptrItems.length == 0) {
|
||||||
|
$scope.addNewNaptr();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$scope.addNewSshfp = function() {
|
$scope.addNewSshfp = function() {
|
||||||
var dataObj = {algorithm:'', type:'', fingerprint: ''};
|
var dataObj = {algorithm:'', type:'', fingerprint: ''};
|
||||||
$scope.currentRecord.sshfpItems.push(dataObj);
|
$scope.currentRecord.sshfpItems.push(dataObj);
|
||||||
|
@@ -76,7 +76,7 @@ angular.module('service.records', [])
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isDotted(record, zoneName) {
|
function isDotted(record, zoneName) {
|
||||||
var canHaveDots = ['PTR', 'NS', 'SOA', 'SRV'];
|
var canHaveDots = ['PTR', 'NS', 'SOA', 'SRV', 'NAPTR'];
|
||||||
|
|
||||||
return canHaveDots.indexOf(record.type) == -1 &&
|
return canHaveDots.indexOf(record.type) == -1 &&
|
||||||
record.name.indexOf(".") != -1 &&
|
record.name.indexOf(".") != -1 &&
|
||||||
@@ -163,6 +163,13 @@ angular.module('service.records', [])
|
|||||||
});
|
});
|
||||||
newRecord.onlyFour = true;
|
newRecord.onlyFour = true;
|
||||||
break;
|
break;
|
||||||
|
case 'NAPTR':
|
||||||
|
newRecord.naptrItems = [];
|
||||||
|
angular.forEach(record.records, function(item) {
|
||||||
|
newRecord.naptrItems.push(item);
|
||||||
|
});
|
||||||
|
newRecord.onlyFour = true;
|
||||||
|
break;
|
||||||
case 'SSHFP':
|
case 'SSHFP':
|
||||||
newRecord.sshfpItems = [];
|
newRecord.sshfpItems = [];
|
||||||
angular.forEach(record.records, function(item) {
|
angular.forEach(record.records, function(item) {
|
||||||
@@ -233,6 +240,17 @@ angular.module('service.records', [])
|
|||||||
"target": record.target});
|
"target": record.target});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'NAPTR':
|
||||||
|
newRecord.records = [];
|
||||||
|
angular.forEach(record.naptrItems, function(record) {
|
||||||
|
newRecord.records.push({"order": Number(record.order),
|
||||||
|
"preference": Number(record.preference),
|
||||||
|
"flags": record.flags,
|
||||||
|
"service": record.service,
|
||||||
|
"regexp": record.regexp,
|
||||||
|
"replacement": record.replacement});
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'SPF':
|
case 'SPF':
|
||||||
newRecord.records = [];
|
newRecord.records = [];
|
||||||
angular.forEach(record.spfRecordData, function(text) {
|
angular.forEach(record.spfRecordData, function(text) {
|
||||||
|
@@ -248,6 +248,86 @@
|
|||||||
</button>
|
</button>
|
||||||
</modal-element>
|
</modal-element>
|
||||||
|
|
||||||
|
<modal-element ng-if="currentRecord.type == 'NAPTR'" label="Record Data">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<tr>
|
||||||
|
<td class="table-col-10"><label class="control-label">Order</label></td>
|
||||||
|
<td class="table-col-10"><label class="control-label">Preference</label></td>
|
||||||
|
<td class="table-col-10"><label class="control-label">Flags</label></td>
|
||||||
|
<td class="table-col-20"><label class="control-label">Service</label></td>
|
||||||
|
<td class="table-col-25"><label class="control-label">Regexp</label></td>
|
||||||
|
<td class="table-col-25"><label class="control-label">Replacement</label></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-repeat="item in currentRecord.naptrItems">
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['order_' + ($index)].$invalid}">
|
||||||
|
<input name="order_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.order"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['preference_' + ($index)].$invalid}">
|
||||||
|
<input name="weight_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.preference"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['flags_' + ($index)].$invalid}">
|
||||||
|
<input name="flags_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.flags"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['service_' + ($index)].$invalid}">
|
||||||
|
<input name="flags_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.service"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['regexp_' + ($index)].$invalid}">
|
||||||
|
<input name="flags_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.regexp"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly">
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td ng-class="{'has-error': addRecordForm.$submitted && addRecordForm['replacement_' + ($index)].$invalid}">
|
||||||
|
<input name="target_{{$index}}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="item.replacement"
|
||||||
|
ng-class="recordModal.details.class"
|
||||||
|
ng-readonly="recordModal.details.readOnly"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger fa fa-times"
|
||||||
|
ng-disabled="disabledStates.indexOf(recordModal.action) > -1"
|
||||||
|
ng-click="deleteNaptr($index)">
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-success"
|
||||||
|
ng-disabled="disabledStates.indexOf(recordModal.action) > -1" ng-click="addNewNaptr()">
|
||||||
|
Add Row
|
||||||
|
</button>
|
||||||
|
</modal-element>
|
||||||
|
|
||||||
<modal-element ng-if="currentRecord.type == 'SSHFP'" label="Record Data">
|
<modal-element ng-if="currentRecord.type == 'SSHFP'" label="Record Data">
|
||||||
<table class="table table-condensed">
|
<table class="table table-condensed">
|
||||||
<tr>
|
<tr>
|
||||||
|
Reference in New Issue
Block a user