mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-09-02 23:35:18 +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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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.SPF => RecordType.SPF
|
||||
case DNS.Type.SRV => RecordType.SRV
|
||||
case DNS.Type.NAPTR => RecordType.NAPTR
|
||||
case DNS.Type.SSHFP => RecordType.SSHFP
|
||||
case DNS.Type.TXT => RecordType.TXT
|
||||
case _ => RecordType.UNKNOWN
|
||||
@@ -150,6 +151,7 @@ trait DnsConversions {
|
||||
case x: DNS.SOARecord => fromSOARecord(x, zoneName, zoneId)
|
||||
case x: DNS.SPFRecord => fromSPFRecord(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.TXTRecord => fromTXTRecord(x, zoneName, zoneId)
|
||||
case _ => fromUnknownRecordType(r, zoneName, zoneId)
|
||||
@@ -267,6 +269,18 @@ trait DnsConversions {
|
||||
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 =
|
||||
fromDnsRecord(r, zoneName, zoneId) { data =>
|
||||
List(SSHFPData(data.getAlgorithm, data.getDigestType, new String(data.getFingerPrint)))
|
||||
@@ -339,6 +353,18 @@ trait DnsConversions {
|
||||
port,
|
||||
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) =>
|
||||
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.SSHFP => DNS.Type.SSHFP
|
||||
case RecordType.SRV => DNS.Type.SRV
|
||||
case RecordType.NAPTR => DNS.Type.NAPTR
|
||||
case RecordType.TXT => DNS.Type.TXT
|
||||
}
|
||||
|
||||
|
@@ -92,7 +92,7 @@ object RecordSetValidations {
|
||||
case NS => nsValidations(newRecordSet, zone)
|
||||
case SOA => soaValidations(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 _ => isNotDotted(newRecordSet, zone)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ object RecordSetValidations {
|
||||
case NS => nsValidations(newRecordSet, zone, Some(oldRecordSet))
|
||||
case SOA => soaValidations(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 _ => isNotDotted(newRecordSet, zone)
|
||||
}
|
||||
|
@@ -106,7 +106,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
changesWithUserIds
|
||||
.filter { chg =>
|
||||
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)
|
||||
.grouped(1000)
|
||||
|
@@ -57,6 +57,7 @@ trait DnsJsonProtocol extends JsonValidation {
|
||||
SOASerializer,
|
||||
SPFSerializer,
|
||||
SRVSerializer,
|
||||
NAPTRSerializer,
|
||||
SSHFPSerializer,
|
||||
TXTSerializer,
|
||||
JsonV[ZoneACL]
|
||||
@@ -263,6 +264,7 @@ trait DnsJsonProtocol extends JsonValidation {
|
||||
case RecordType.SOA => js.required[List[SOAData]]("Missing SOA Records")
|
||||
case RecordType.SPF => js.required[List[SPFData]]("Missing SPF 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.TXT => js.required[List[TXTData]]("Missing TXT Records")
|
||||
case _ => s"Unsupported type $typ, valid types include ${RecordType.values}".invalidNel
|
||||
@@ -417,6 +419,44 @@ trait DnsJsonProtocol extends JsonValidation {
|
||||
).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] {
|
||||
override def fromJson(js: JValue): ValidatedNel[String, SSHFPData] =
|
||||
(
|
||||
|
@@ -142,6 +142,16 @@ class DnsConversionsSpec
|
||||
DateTime.now,
|
||||
None,
|
||||
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(
|
||||
testZone.id,
|
||||
"sshfp-record",
|
||||
@@ -350,6 +360,11 @@ class DnsConversionsSpec
|
||||
verifyMatch(result, testSRV)
|
||||
}
|
||||
|
||||
"convert NAPTR record set" in {
|
||||
val result = rightValue(toDnsRRset(testNAPTR, testZoneName))
|
||||
verifyMatch(result, testNAPTR)
|
||||
}
|
||||
|
||||
"convert TXT record set" in {
|
||||
val result = rightValue(toDnsRRset(testTXT, testZoneName))
|
||||
verifyMatch(result, testTXT)
|
||||
@@ -464,6 +479,9 @@ class DnsConversionsSpec
|
||||
"convert to/from RecordType SRV" in {
|
||||
verifyMatch(testSRV, roundTrip(testSRV))
|
||||
}
|
||||
"convert to/from RecordType NAPTR" in {
|
||||
verifyMatch(testNAPTR, roundTrip(testNAPTR))
|
||||
}
|
||||
"convert to/from RecordType SSHFP" in {
|
||||
verifyMatch(testSSHFP, roundTrip(testSSHFP))
|
||||
}
|
||||
@@ -509,6 +527,9 @@ class DnsConversionsSpec
|
||||
"support SRV" in {
|
||||
toDnsRecordType(RecordType.SRV) shouldBe DNS.Type.SRV
|
||||
}
|
||||
"support NAPTR" in {
|
||||
toDnsRecordType(RecordType.NAPTR) shouldBe DNS.Type.NAPTR
|
||||
}
|
||||
"support TXT" in {
|
||||
toDnsRecordType(RecordType.TXT) shouldBe DNS.Type.TXT
|
||||
}
|
||||
|
@@ -55,11 +55,21 @@ class RecordSetValidationsSpec
|
||||
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 {
|
||||
val error = leftValue(validRecordTypes(srv, zoneIp6))
|
||||
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 {
|
||||
validRecordTypes(aaaa, okZone) should be(right)
|
||||
}
|
||||
@@ -214,6 +224,29 @@ class RecordSetValidationsSpec
|
||||
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 {
|
||||
"return success for a PTR record with dots in a reverse zone" in {
|
||||
val test = ptrIp4.copy(name = "10.1.2.")
|
||||
|
@@ -154,6 +154,15 @@ class ProtobufConversionsSpec
|
||||
DateTime.now,
|
||||
None,
|
||||
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(
|
||||
zone.id,
|
||||
"sshfp",
|
||||
@@ -527,6 +536,10 @@ class ProtobufConversionsSpec
|
||||
fromPB(toPB(srv)) shouldBe srv
|
||||
}
|
||||
|
||||
"convert from protobuf for NAPTR recordset" in {
|
||||
fromPB(toPB(naptr)) shouldBe naptr
|
||||
}
|
||||
|
||||
"convert from protobuf for SSHFP recordset" in {
|
||||
fromPB(toPB(sshfp)) shouldBe sshfp
|
||||
}
|
||||
@@ -665,6 +678,25 @@ class ProtobufConversionsSpec
|
||||
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 {
|
||||
val from = SSHFPData(1, 2, "fingerprint")
|
||||
val pb = toPB(from)
|
||||
|
@@ -261,6 +261,16 @@ class RecordSetRoutingSpec
|
||||
None,
|
||||
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(
|
||||
okZone.id,
|
||||
"sshfp",
|
||||
@@ -325,6 +335,7 @@ class RecordSetRoutingSpec
|
||||
soa.id -> soa,
|
||||
spf.id -> spf,
|
||||
srv.id -> srv,
|
||||
naptr.id -> naptr,
|
||||
sshfp.id -> sshfp,
|
||||
txt.id -> txt
|
||||
)
|
||||
@@ -908,6 +919,7 @@ class RecordSetRoutingSpec
|
||||
validateErrors(testRecordBase(RecordType.SOA, JNothing), "Missing SOA Records")
|
||||
validateErrors(testRecordBase(RecordType.SPF, JNothing), "Missing SPF 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.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 {
|
||||
validateCreateRecordType(sshfp)
|
||||
}
|
||||
|
@@ -444,6 +444,55 @@ class VinylDNSJsonProtocolSpec
|
||||
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 {
|
||||
val recordSetJValue: JValue =
|
||||
("zoneId" -> "1") ~~
|
||||
|
@@ -94,6 +94,15 @@ message SRVData {
|
||||
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 {
|
||||
required int32 algorithm = 1;
|
||||
required int32 typ = 2;
|
||||
|
@@ -73,6 +73,26 @@ object SRVData {
|
||||
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 TXTData(text: String) extends RecordData
|
||||
|
@@ -22,7 +22,7 @@ import org.joda.time.DateTime
|
||||
|
||||
object RecordType extends Enumeration {
|
||||
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 {
|
||||
|
@@ -165,6 +165,7 @@ trait ProtobufConversions {
|
||||
case RecordType.SOA => fromPB(VinylDNSProto.SOAData.parseFrom(rd.getData))
|
||||
case RecordType.SPF => fromPB(VinylDNSProto.SPFData.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.TXT => fromPB(VinylDNSProto.TXTData.parseFrom(rd.getData))
|
||||
}
|
||||
@@ -203,6 +204,15 @@ trait ProtobufConversions {
|
||||
def fromPB(data: VinylDNSProto.SRVData): SRVData =
|
||||
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 =
|
||||
SSHFPData(data.getAlgorithm, data.getTyp, data.getFingerPrint)
|
||||
|
||||
@@ -283,6 +293,17 @@ trait ProtobufConversions {
|
||||
.setWeight(data.weight)
|
||||
.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 =
|
||||
VinylDNSProto.SSHFPData
|
||||
.newBuilder()
|
||||
@@ -307,6 +328,7 @@ trait ProtobufConversions {
|
||||
case x: SOAData => toPB(x)
|
||||
case x: SPFData => toPB(x)
|
||||
case x: SRVData => toPB(x)
|
||||
case x: NAPTRData => toPB(x)
|
||||
case x: SSHFPData => toPB(x)
|
||||
case x: TXTData => toPB(x)
|
||||
}
|
||||
|
@@ -108,6 +108,16 @@ object TestRecordSetData {
|
||||
None,
|
||||
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(
|
||||
okZone.id,
|
||||
"mx",
|
||||
|
@@ -63,5 +63,11 @@ class RecordSetSpec extends WordSpec with Matchers {
|
||||
|
||||
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.SOA => 11
|
||||
case RecordType.DS => 12
|
||||
case RecordType.NAPTR => 13
|
||||
case RecordType.UNKNOWN => unknownRecordType
|
||||
}
|
||||
|
||||
|
@@ -265,6 +265,21 @@
|
||||
</span>
|
||||
</ul>
|
||||
</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'">
|
||||
<ul class="table-cell-list">
|
||||
<li ng-repeat="item in record.sshfpItems track by $index"
|
||||
|
@@ -63,7 +63,7 @@ angular.module('controller.manageZones', [])
|
||||
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
|
||||
|
@@ -24,7 +24,7 @@ angular.module('controller.records', [])
|
||||
$scope.query = "";
|
||||
$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},
|
||||
{name: '(4) Ed25519', number: 4}];
|
||||
$scope.sshfpTypes = [{name: '(1) SHA-1', number: 1}, {name: '(2) SHA-256', number: 2}];
|
||||
@@ -92,6 +92,7 @@ angular.module('controller.records', [])
|
||||
ttl: 300,
|
||||
mxItems: [{preference:'', exchange:''}],
|
||||
srvItems: [{priority:'', weight:'', port:'', target:''}],
|
||||
naptrItems: [{order:'', preference:'', flags:'', service:'', regexp:'', replacement:''}],
|
||||
sshfpItems: [{algorithm:'', type:'', fingerprint:''}]
|
||||
};
|
||||
$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() {
|
||||
var dataObj = {algorithm:'', type:'', fingerprint: ''};
|
||||
$scope.currentRecord.sshfpItems.push(dataObj);
|
||||
|
@@ -76,7 +76,7 @@ angular.module('service.records', [])
|
||||
}
|
||||
|
||||
function isDotted(record, zoneName) {
|
||||
var canHaveDots = ['PTR', 'NS', 'SOA', 'SRV'];
|
||||
var canHaveDots = ['PTR', 'NS', 'SOA', 'SRV', 'NAPTR'];
|
||||
|
||||
return canHaveDots.indexOf(record.type) == -1 &&
|
||||
record.name.indexOf(".") != -1 &&
|
||||
@@ -163,6 +163,13 @@ angular.module('service.records', [])
|
||||
});
|
||||
newRecord.onlyFour = true;
|
||||
break;
|
||||
case 'NAPTR':
|
||||
newRecord.naptrItems = [];
|
||||
angular.forEach(record.records, function(item) {
|
||||
newRecord.naptrItems.push(item);
|
||||
});
|
||||
newRecord.onlyFour = true;
|
||||
break;
|
||||
case 'SSHFP':
|
||||
newRecord.sshfpItems = [];
|
||||
angular.forEach(record.records, function(item) {
|
||||
@@ -233,6 +240,17 @@ angular.module('service.records', [])
|
||||
"target": record.target});
|
||||
});
|
||||
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':
|
||||
newRecord.records = [];
|
||||
angular.forEach(record.spfRecordData, function(text) {
|
||||
|
@@ -248,6 +248,86 @@
|
||||
</button>
|
||||
</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">
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
|
Reference in New Issue
Block a user