2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-09-01 23:05:15 +00:00

Adding NAPTR support (#594)

* Adding NAPTR support
This commit is contained in:
Thomas Pressnell
2019-04-24 16:22:05 +01:00
committed by Paul Cleary
parent 5040d07c00
commit 77c4536e37
22 changed files with 542 additions and 7 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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] =
(

View File

@@ -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
}

View File

@@ -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.")

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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") ~~

View File

@@ -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;

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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."))
}
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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>