mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-29 05:18:08 +00:00
Merge pull request #1187 from Aravindh-Raju/aravindhr/allow-dotted-hosts
Allow dotted hosts creation using config
This commit is contained in:
commit
03b3cf4bd5
@ -163,6 +163,28 @@ vinyldns {
|
|||||||
"ns1.parent.com4."
|
"ns1.parent.com4."
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts
|
||||||
|
dotted-hosts = {
|
||||||
|
# for local testing
|
||||||
|
allowed-settings = [
|
||||||
|
{
|
||||||
|
zone = "*mmy."
|
||||||
|
user-list = ["testuser"]
|
||||||
|
group-list = ["dummy-group"]
|
||||||
|
record-types = ["AAAA"]
|
||||||
|
dots-limit = 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# for wildcard zones. Settings will be applied to all matching zones
|
||||||
|
zone = "parent.com."
|
||||||
|
user-list = ["professor", "testuser"]
|
||||||
|
group-list = ["testing-group"]
|
||||||
|
record-types = ["A", "CNAME"]
|
||||||
|
dots-limit = 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production
|
# Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production
|
||||||
crypto {
|
crypto {
|
||||||
type = "vinyldns.core.crypto.NoOpCrypto"
|
type = "vinyldns.core.crypto.NoOpCrypto"
|
||||||
|
@ -32,7 +32,6 @@ import vinyldns.api.domain.access.AccessValidations
|
|||||||
import vinyldns.api.domain.zone._
|
import vinyldns.api.domain.zone._
|
||||||
import vinyldns.api.engine.TestMessageQueue
|
import vinyldns.api.engine.TestMessageQueue
|
||||||
import vinyldns.mysql.TransactionProvider
|
import vinyldns.mysql.TransactionProvider
|
||||||
import vinyldns.core.TestMembershipData._
|
|
||||||
import vinyldns.core.TestZoneData.testConnection
|
import vinyldns.core.TestZoneData.testConnection
|
||||||
import vinyldns.core.domain.{Fqdn, HighValueDomainError}
|
import vinyldns.core.domain.{Fqdn, HighValueDomainError}
|
||||||
import vinyldns.core.domain.auth.AuthPrincipal
|
import vinyldns.core.domain.auth.AuthPrincipal
|
||||||
@ -64,13 +63,16 @@ class RecordSetServiceIntegrationSpec
|
|||||||
private var testRecordSetService: RecordSetServiceAlgebra = _
|
private var testRecordSetService: RecordSetServiceAlgebra = _
|
||||||
|
|
||||||
private val user = User("live-test-user", "key", "secret")
|
private val user = User("live-test-user", "key", "secret")
|
||||||
|
private val testUser = User("testuser", "key", "secret")
|
||||||
private val user2 = User("shared-record-test-user", "key-shared", "secret-shared")
|
private val user2 = User("shared-record-test-user", "key-shared", "secret-shared")
|
||||||
private val group = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id))
|
private val group = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id))
|
||||||
|
private val dummyGroup = Group(s"dummy-group", "test@test.com", adminUserIds = Set(testUser.id))
|
||||||
private val group2 = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id, user2.id))
|
private val group2 = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id, user2.id))
|
||||||
private val sharedGroup =
|
private val sharedGroup =
|
||||||
Group(s"test-shared-group", "test@test.com", adminUserIds = Set(user.id, user2.id))
|
Group(s"test-shared-group", "test@test.com", adminUserIds = Set(user.id, user2.id))
|
||||||
private val auth = AuthPrincipal(user, Seq(group.id, sharedGroup.id))
|
private val auth = AuthPrincipal(user, Seq(group.id, sharedGroup.id))
|
||||||
private val auth2 = AuthPrincipal(user2, Seq(sharedGroup.id, group2.id))
|
private val auth2 = AuthPrincipal(user2, Seq(sharedGroup.id, group2.id))
|
||||||
|
val dummyAuth: AuthPrincipal = AuthPrincipal(testUser, Seq(dummyGroup.id))
|
||||||
|
|
||||||
private val zone = Zone(
|
private val zone = Zone(
|
||||||
s"live-zone-test.",
|
s"live-zone-test.",
|
||||||
@ -167,6 +169,14 @@ class RecordSetServiceIntegrationSpec
|
|||||||
adminGroupId = group.id
|
adminGroupId = group.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val dummyZone = Zone(
|
||||||
|
s"dummy.",
|
||||||
|
"test@test.com",
|
||||||
|
status = ZoneStatus.Active,
|
||||||
|
connection = testConnection,
|
||||||
|
adminGroupId = dummyGroup.id
|
||||||
|
)
|
||||||
|
|
||||||
private val highValueDomainRecord = RecordSet(
|
private val highValueDomainRecord = RecordSet(
|
||||||
zone.id,
|
zone.id,
|
||||||
"high-value-domain-existing",
|
"high-value-domain-existing",
|
||||||
@ -254,8 +264,8 @@ class RecordSetServiceIntegrationSpec
|
|||||||
groupRepo.save(db, group)
|
groupRepo.save(db, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
List(group, group2, sharedGroup).traverse(g => saveGroupData(groupRepo, g).void).unsafeRunSync()
|
List(group, group2, sharedGroup, dummyGroup).traverse(g => saveGroupData(groupRepo, g).void).unsafeRunSync()
|
||||||
List(zone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone)
|
List(zone, dummyZone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone)
|
||||||
.traverse(
|
.traverse(
|
||||||
z => zoneRepo.save(z)
|
z => zoneRepo.save(z)
|
||||||
)
|
)
|
||||||
@ -300,6 +310,7 @@ class RecordSetServiceIntegrationSpec
|
|||||||
mockBackendResolver,
|
mockBackendResolver,
|
||||||
false,
|
false,
|
||||||
vinyldnsConfig.highValueDomainConfig,
|
vinyldnsConfig.highValueDomainConfig,
|
||||||
|
vinyldnsConfig.dottedHostsConfig,
|
||||||
vinyldnsConfig.serverConfig.approvedNameServers,
|
vinyldnsConfig.serverConfig.approvedNameServers,
|
||||||
useRecordSetCache = true
|
useRecordSetCache = true
|
||||||
)
|
)
|
||||||
@ -338,6 +349,70 @@ class RecordSetServiceIntegrationSpec
|
|||||||
.name shouldBe "zone-test-add-records."
|
.name shouldBe "zone-test-add-records."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"create dotted record fails if it doesn't satisfy dotted hosts config" in {
|
||||||
|
val newRecord = RecordSet(
|
||||||
|
zoneTestAddRecords.id,
|
||||||
|
"test.dot",
|
||||||
|
A,
|
||||||
|
38400,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(AData("10.1.1.1"))
|
||||||
|
)
|
||||||
|
val result =
|
||||||
|
testRecordSetService
|
||||||
|
.addRecordSet(newRecord, auth)
|
||||||
|
.value
|
||||||
|
.unsafeRunSync()
|
||||||
|
leftValue(result) shouldBe a[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"create dotted record succeeds if it satisfies all dotted hosts config" in {
|
||||||
|
val newRecord = RecordSet(
|
||||||
|
dummyZone.id,
|
||||||
|
"test.dotted",
|
||||||
|
AAAA,
|
||||||
|
38400,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(AAAAData("fd69:27cc:fe91::60"))
|
||||||
|
)
|
||||||
|
// succeeds as zone, user and record type is allowed as defined in application.conf
|
||||||
|
val result =
|
||||||
|
testRecordSetService
|
||||||
|
.addRecordSet(newRecord, dummyAuth)
|
||||||
|
.value
|
||||||
|
.unsafeRunSync()
|
||||||
|
rightValue(result)
|
||||||
|
.asInstanceOf[RecordSetChange]
|
||||||
|
.recordSet
|
||||||
|
.name shouldBe "test.dotted"
|
||||||
|
}
|
||||||
|
|
||||||
|
"fail creating dotted record if it satisfies all dotted hosts config except dots-limit for the zone" in {
|
||||||
|
val newRecord = RecordSet(
|
||||||
|
dummyZone.id,
|
||||||
|
"test.dotted.more.dots.than.allowed",
|
||||||
|
AAAA,
|
||||||
|
38400,
|
||||||
|
RecordSetStatus.Active,
|
||||||
|
DateTime.now,
|
||||||
|
None,
|
||||||
|
List(AAAAData("fd69:27cc:fe91::60"))
|
||||||
|
)
|
||||||
|
|
||||||
|
// The number of dots allowed in the record name for this zone as defined in the config is 3.
|
||||||
|
// Creating with 4 dots results in an error
|
||||||
|
val result =
|
||||||
|
testRecordSetService
|
||||||
|
.addRecordSet(newRecord, dummyAuth)
|
||||||
|
.value
|
||||||
|
.unsafeRunSync()
|
||||||
|
leftValue(result) shouldBe a[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
"update apex A record and add trailing dot" in {
|
"update apex A record and add trailing dot" in {
|
||||||
val newRecord = apexTestRecordA.copy(ttl = 200)
|
val newRecord = apexTestRecordA.copy(ttl = 200)
|
||||||
val result = testRecordSetService
|
val result = testRecordSetService
|
||||||
|
@ -165,6 +165,19 @@ vinyldns {
|
|||||||
"ns1.parent.com4."
|
"ns1.parent.com4."
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts
|
||||||
|
dotted-hosts = {
|
||||||
|
allowed-settings = [
|
||||||
|
{
|
||||||
|
zone = "zonenamehere."
|
||||||
|
user-list = []
|
||||||
|
group-list = []
|
||||||
|
record-types = []
|
||||||
|
dots-limit = 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production
|
# Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production
|
||||||
crypto {
|
crypto {
|
||||||
type = "vinyldns.core.crypto.NoOpCrypto"
|
type = "vinyldns.core.crypto.NoOpCrypto"
|
||||||
|
@ -90,6 +90,29 @@ vinyldns {
|
|||||||
"ns1.parent.com."
|
"ns1.parent.com."
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts
|
||||||
|
dotted-hosts = {
|
||||||
|
# for local testing
|
||||||
|
allowed-settings = [
|
||||||
|
{
|
||||||
|
# for wildcard zones. Settings will be applied to all matching zones
|
||||||
|
zone = "*ent.com*."
|
||||||
|
user-list = ["ok"]
|
||||||
|
group-list = ["dummy-group"]
|
||||||
|
record-types = ["CNAME"]
|
||||||
|
dots-limit = 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# for wildcard zones. Settings will be applied to all matching zones
|
||||||
|
zone = "dummy*."
|
||||||
|
user-list = ["sharedZoneUser"]
|
||||||
|
group-list = ["history-group1"]
|
||||||
|
record-types = ["A"]
|
||||||
|
dots-limit = 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# color should be green or blue, used in order to do blue/green deployment
|
# color should be green or blue, used in order to do blue/green deployment
|
||||||
color = "green"
|
color = "green"
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ object Boot extends App {
|
|||||||
backendResolver,
|
backendResolver,
|
||||||
vinyldnsConfig.serverConfig.validateRecordLookupAgainstDnsBackend,
|
vinyldnsConfig.serverConfig.validateRecordLookupAgainstDnsBackend,
|
||||||
vinyldnsConfig.highValueDomainConfig,
|
vinyldnsConfig.highValueDomainConfig,
|
||||||
|
vinyldnsConfig.dottedHostsConfig,
|
||||||
vinyldnsConfig.serverConfig.approvedNameServers,
|
vinyldnsConfig.serverConfig.approvedNameServers,
|
||||||
vinyldnsConfig.serverConfig.useRecordSetCache
|
vinyldnsConfig.serverConfig.useRecordSetCache
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.config
|
||||||
|
|
||||||
|
import pureconfig.ConfigReader
|
||||||
|
import pureconfig.generic.auto._
|
||||||
|
|
||||||
|
final case class ZoneAuthConfigs(zone: String, userList: List[String], groupList: List[String], recordTypes: List[String], dotsLimit: Int)
|
||||||
|
final case class DottedHostsConfig(zoneAuthConfigs: List[ZoneAuthConfigs])
|
||||||
|
|
||||||
|
object DottedHostsConfig {
|
||||||
|
implicit val configReader: ConfigReader[DottedHostsConfig] =
|
||||||
|
ConfigReader.forProduct1[DottedHostsConfig, List[ZoneAuthConfigs]](
|
||||||
|
"allowed-settings",
|
||||||
|
)(zoneAuthConfigs =>
|
||||||
|
DottedHostsConfig(zoneAuthConfigs))
|
||||||
|
}
|
@ -47,6 +47,7 @@ final case class VinylDNSConfig(
|
|||||||
notifierConfigs: List[NotifierConfig],
|
notifierConfigs: List[NotifierConfig],
|
||||||
dataStoreConfigs: List[DataStoreConfig],
|
dataStoreConfigs: List[DataStoreConfig],
|
||||||
backendConfigs: BackendConfigs,
|
backendConfigs: BackendConfigs,
|
||||||
|
dottedHostsConfig: DottedHostsConfig,
|
||||||
configuredDnsConnections: ConfiguredDnsConnections,
|
configuredDnsConnections: ConfiguredDnsConnections,
|
||||||
apiMetricSettings: APIMetricsSettings,
|
apiMetricSettings: APIMetricsSettings,
|
||||||
crypto: CryptoAlgebra,
|
crypto: CryptoAlgebra,
|
||||||
@ -85,6 +86,7 @@ object VinylDNSConfig {
|
|||||||
serverConfig <- loadIO[ServerConfig](config, "vinyldns")
|
serverConfig <- loadIO[ServerConfig](config, "vinyldns")
|
||||||
batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns")
|
batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns")
|
||||||
backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend")
|
backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend")
|
||||||
|
dottedHostsConfig <- loadIO[DottedHostsConfig](config, "vinyldns.dotted-hosts")
|
||||||
httpConfig <- loadIO[HttpConfig](config, "vinyldns.rest")
|
httpConfig <- loadIO[HttpConfig](config, "vinyldns.rest")
|
||||||
hvdConfig <- loadIO[HighValueDomainConfig](config, "vinyldns.high-value-domains")
|
hvdConfig <- loadIO[HighValueDomainConfig](config, "vinyldns.high-value-domains")
|
||||||
scheduledChangesConfig <- loadIO[ScheduledChangesConfig](config, "vinyldns")
|
scheduledChangesConfig <- loadIO[ScheduledChangesConfig](config, "vinyldns")
|
||||||
@ -110,6 +112,7 @@ object VinylDNSConfig {
|
|||||||
notifierConfigs,
|
notifierConfigs,
|
||||||
dataStoreConfigs,
|
dataStoreConfigs,
|
||||||
backendConfigs,
|
backendConfigs,
|
||||||
|
dottedHostsConfig,
|
||||||
connections,
|
connections,
|
||||||
metricSettings,
|
metricSettings,
|
||||||
crypto,
|
crypto,
|
||||||
|
@ -32,19 +32,8 @@ import vinyldns.core.domain.auth.AuthPrincipal
|
|||||||
import vinyldns.core.domain.batch.BatchChangeApprovalStatus.BatchChangeApprovalStatus
|
import vinyldns.core.domain.batch.BatchChangeApprovalStatus.BatchChangeApprovalStatus
|
||||||
import vinyldns.core.domain.batch._
|
import vinyldns.core.domain.batch._
|
||||||
import vinyldns.core.domain.batch.BatchChangeApprovalStatus._
|
import vinyldns.core.domain.batch.BatchChangeApprovalStatus._
|
||||||
import vinyldns.core.domain.{
|
import vinyldns.core.domain.{CnameAtZoneApexError, SingleChangeError, UserIsNotAuthorizedError, ZoneDiscoveryError}
|
||||||
CnameAtZoneApexError,
|
import vinyldns.core.domain.membership.{Group, GroupRepository, ListUsersResults, User, UserRepository}
|
||||||
SingleChangeError,
|
|
||||||
UserIsNotAuthorizedError,
|
|
||||||
ZoneDiscoveryError
|
|
||||||
}
|
|
||||||
import vinyldns.core.domain.membership.{
|
|
||||||
Group,
|
|
||||||
GroupRepository,
|
|
||||||
ListUsersResults,
|
|
||||||
User,
|
|
||||||
UserRepository
|
|
||||||
}
|
|
||||||
import vinyldns.core.domain.record.RecordType._
|
import vinyldns.core.domain.record.RecordType._
|
||||||
import vinyldns.core.domain.record.RecordSetRepository
|
import vinyldns.core.domain.record.RecordSetRepository
|
||||||
import vinyldns.core.domain.zone.ZoneRepository
|
import vinyldns.core.domain.zone.ZoneRepository
|
||||||
|
@ -45,10 +45,10 @@ trait BatchChangeValidationsAlgebra {
|
|||||||
): ValidatedBatch[ChangeInput]
|
): ValidatedBatch[ChangeInput]
|
||||||
|
|
||||||
def validateChangesWithContext(
|
def validateChangesWithContext(
|
||||||
groupedChanges: ChangeForValidationMap,
|
groupedChanges: ChangeForValidationMap,
|
||||||
auth: AuthPrincipal,
|
auth: AuthPrincipal,
|
||||||
isApproved: Boolean,
|
isApproved: Boolean,
|
||||||
batchOwnerGroupId: Option[String]
|
batchOwnerGroupId: Option[String]
|
||||||
): ValidatedBatch[ChangeForValidation]
|
): ValidatedBatch[ChangeForValidation]
|
||||||
|
|
||||||
def canGetBatchChange(
|
def canGetBatchChange(
|
||||||
@ -273,17 +273,17 @@ class BatchChangeValidations(
|
|||||||
/* context validations */
|
/* context validations */
|
||||||
|
|
||||||
def validateChangesWithContext(
|
def validateChangesWithContext(
|
||||||
groupedChanges: ChangeForValidationMap,
|
groupedChanges: ChangeForValidationMap,
|
||||||
auth: AuthPrincipal,
|
auth: AuthPrincipal,
|
||||||
isApproved: Boolean,
|
isApproved: Boolean,
|
||||||
batchOwnerGroupId: Option[String]
|
batchOwnerGroupId: Option[String]
|
||||||
): ValidatedBatch[ChangeForValidation] =
|
): ValidatedBatch[ChangeForValidation] =
|
||||||
// Updates are a combination of an add and delete for a record with the same name and type in a zone.
|
// Updates are a combination of an add and delete for a record with the same name and type in a zone.
|
||||||
groupedChanges.changes.mapValid {
|
groupedChanges.changes.mapValid {
|
||||||
case add: AddChangeForValidation
|
case add: AddChangeForValidation
|
||||||
if groupedChanges
|
if groupedChanges
|
||||||
.getLogicalChangeType(add.recordKey)
|
.getLogicalChangeType(add.recordKey)
|
||||||
.contains(LogicalChangeType.Add) =>
|
.contains(LogicalChangeType.Add) =>
|
||||||
validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId)
|
validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId)
|
||||||
case addUpdate: AddChangeForValidation =>
|
case addUpdate: AddChangeForValidation =>
|
||||||
validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId)
|
validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId)
|
||||||
@ -409,11 +409,11 @@ class BatchChangeValidations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
def validateAddWithContext(
|
def validateAddWithContext(
|
||||||
change: AddChangeForValidation,
|
change: AddChangeForValidation,
|
||||||
groupedChanges: ChangeForValidationMap,
|
groupedChanges: ChangeForValidationMap,
|
||||||
auth: AuthPrincipal,
|
auth: AuthPrincipal,
|
||||||
isApproved: Boolean,
|
isApproved: Boolean,
|
||||||
ownerGroupId: Option[String]
|
ownerGroupId: Option[String]
|
||||||
): SingleValidation[ChangeForValidation] = {
|
): SingleValidation[ChangeForValidation] = {
|
||||||
val typedValidations = change.inputChange.typ match {
|
val typedValidations = change.inputChange.typ match {
|
||||||
case A | AAAA | MX =>
|
case A | AAAA | MX =>
|
||||||
|
@ -28,7 +28,7 @@ import vinyldns.core.queue.MessageQueue
|
|||||||
import cats.data._
|
import cats.data._
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import org.xbill.DNS.ReverseMap
|
import org.xbill.DNS.ReverseMap
|
||||||
import vinyldns.api.config.HighValueDomainConfig
|
import vinyldns.api.config.{ZoneAuthConfigs, DottedHostsConfig, HighValueDomainConfig}
|
||||||
import vinyldns.api.domain.DomainValidations.{validateIpv4Address, validateIpv6Address}
|
import vinyldns.api.domain.DomainValidations.{validateIpv4Address, validateIpv6Address}
|
||||||
import vinyldns.api.domain.access.AccessValidationsAlgebra
|
import vinyldns.api.domain.access.AccessValidationsAlgebra
|
||||||
import vinyldns.core.domain.record.NameSort.NameSort
|
import vinyldns.core.domain.record.NameSort.NameSort
|
||||||
@ -46,6 +46,7 @@ object RecordSetService {
|
|||||||
backendResolver: BackendResolver,
|
backendResolver: BackendResolver,
|
||||||
validateRecordLookupAgainstDnsBackend: Boolean,
|
validateRecordLookupAgainstDnsBackend: Boolean,
|
||||||
highValueDomainConfig: HighValueDomainConfig,
|
highValueDomainConfig: HighValueDomainConfig,
|
||||||
|
dottedHostsConfig: DottedHostsConfig,
|
||||||
approvedNameServers: List[Regex],
|
approvedNameServers: List[Regex],
|
||||||
useRecordSetCache: Boolean
|
useRecordSetCache: Boolean
|
||||||
): RecordSetService =
|
): RecordSetService =
|
||||||
@ -61,6 +62,7 @@ object RecordSetService {
|
|||||||
backendResolver,
|
backendResolver,
|
||||||
validateRecordLookupAgainstDnsBackend,
|
validateRecordLookupAgainstDnsBackend,
|
||||||
highValueDomainConfig,
|
highValueDomainConfig,
|
||||||
|
dottedHostsConfig,
|
||||||
approvedNameServers,
|
approvedNameServers,
|
||||||
useRecordSetCache
|
useRecordSetCache
|
||||||
)
|
)
|
||||||
@ -78,6 +80,7 @@ class RecordSetService(
|
|||||||
backendResolver: BackendResolver,
|
backendResolver: BackendResolver,
|
||||||
validateRecordLookupAgainstDnsBackend: Boolean,
|
validateRecordLookupAgainstDnsBackend: Boolean,
|
||||||
highValueDomainConfig: HighValueDomainConfig,
|
highValueDomainConfig: HighValueDomainConfig,
|
||||||
|
dottedHostsConfig: DottedHostsConfig,
|
||||||
approvedNameServers: List[Regex],
|
approvedNameServers: List[Regex],
|
||||||
useRecordSetCache: Boolean
|
useRecordSetCache: Boolean
|
||||||
) extends RecordSetServiceAlgebra {
|
) extends RecordSetServiceAlgebra {
|
||||||
@ -88,6 +91,7 @@ class RecordSetService(
|
|||||||
def addRecordSet(recordSet: RecordSet, auth: AuthPrincipal): Result[ZoneCommandResult] =
|
def addRecordSet(recordSet: RecordSet, auth: AuthPrincipal): Result[ZoneCommandResult] =
|
||||||
for {
|
for {
|
||||||
zone <- getZone(recordSet.zoneId)
|
zone <- getZone(recordSet.zoneId)
|
||||||
|
authZones = dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
change <- RecordSetChangeGenerator.forAdd(recordSet, zone, Some(auth)).toResult
|
change <- RecordSetChangeGenerator.forAdd(recordSet, zone, Some(auth)).toResult
|
||||||
// because changes happen to the RS in forAdd itself, converting 1st and validating on that
|
// because changes happen to the RS in forAdd itself, converting 1st and validating on that
|
||||||
rsForValidations = change.recordSet
|
rsForValidations = change.recordSet
|
||||||
@ -107,13 +111,27 @@ class RecordSetService(
|
|||||||
ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId)
|
ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId)
|
||||||
_ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult
|
_ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult
|
||||||
_ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult
|
_ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult
|
||||||
|
allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]]
|
||||||
|
isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth)
|
||||||
|
isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean]
|
||||||
|
isAllowedUser = isInAllowedUsers || isUserInAllowedGroups
|
||||||
|
isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations)
|
||||||
|
isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed
|
||||||
|
allowedDotsLimit = getAllowedDotsLimit(zone, dottedHostsConfig)
|
||||||
|
recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean]
|
||||||
_ <- typeSpecificValidations(
|
_ <- typeSpecificValidations(
|
||||||
rsForValidations,
|
rsForValidations,
|
||||||
existingRecordsWithName,
|
existingRecordsWithName,
|
||||||
zone,
|
zone,
|
||||||
None,
|
None,
|
||||||
approvedNameServers
|
approvedNameServers,
|
||||||
|
recordFqdnDoesNotAlreadyExist,
|
||||||
|
allowedZoneList,
|
||||||
|
isRecordTypeAndUserAllowed,
|
||||||
|
allowedDotsLimit
|
||||||
).toResult
|
).toResult
|
||||||
|
_ <- if(allowedZoneList.contains(zone.name)) checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult else ().toResult
|
||||||
|
_ <- if(allowedZoneList.contains(zone.name)) isNotApexEndsWithDot(rsForValidations, zone).toResult else ().toResult
|
||||||
_ <- messageQueue.send(change).toResult[Unit]
|
_ <- messageQueue.send(change).toResult[Unit]
|
||||||
} yield change
|
} yield change
|
||||||
|
|
||||||
@ -143,13 +161,28 @@ class RecordSetService(
|
|||||||
validateRecordLookupAgainstDnsBackend
|
validateRecordLookupAgainstDnsBackend
|
||||||
)
|
)
|
||||||
_ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult
|
_ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult
|
||||||
|
authZones = dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]]
|
||||||
|
isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth)
|
||||||
|
isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean]
|
||||||
|
isAllowedUser = isInAllowedUsers || isUserInAllowedGroups
|
||||||
|
isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations)
|
||||||
|
isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed
|
||||||
|
allowedDotsLimit = getAllowedDotsLimit(zone, dottedHostsConfig)
|
||||||
|
recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean]
|
||||||
_ <- typeSpecificValidations(
|
_ <- typeSpecificValidations(
|
||||||
rsForValidations,
|
rsForValidations,
|
||||||
existingRecordsWithName,
|
existingRecordsWithName,
|
||||||
zone,
|
zone,
|
||||||
Some(existing),
|
Some(existing),
|
||||||
approvedNameServers
|
approvedNameServers,
|
||||||
|
recordFqdnDoesNotAlreadyExist,
|
||||||
|
allowedZoneList,
|
||||||
|
isRecordTypeAndUserAllowed,
|
||||||
|
allowedDotsLimit
|
||||||
).toResult
|
).toResult
|
||||||
|
_ <- if(existing.name == rsForValidations.name) ().toResult else if(allowedZoneList.contains(zone.name)) checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult else ().toResult
|
||||||
|
_ <- if(allowedZoneList.contains(zone.name)) isNotApexEndsWithDot(rsForValidations, zone).toResult else ().toResult
|
||||||
_ <- messageQueue.send(change).toResult[Unit]
|
_ <- messageQueue.send(change).toResult[Unit]
|
||||||
} yield change
|
} yield change
|
||||||
|
|
||||||
@ -169,6 +202,178 @@ class RecordSetService(
|
|||||||
_ <- messageQueue.send(change).toResult[Unit]
|
_ <- messageQueue.send(change).toResult[Unit]
|
||||||
} yield change
|
} yield change
|
||||||
|
|
||||||
|
// For dotted hosts. Check if a record that may conflict with dotted host exist or not
|
||||||
|
def recordFQDNDoesNotExist(newRecordSet: RecordSet, zone: Zone): IO[Boolean] = {
|
||||||
|
// Use fqdn for searching through `recordset` mysql table to see if it already exist
|
||||||
|
val newRecordFqdn = if(newRecordSet.name != zone.name) newRecordSet.name + "." + zone.name else newRecordSet.name
|
||||||
|
|
||||||
|
for {
|
||||||
|
record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn))
|
||||||
|
isRecordAlreadyExist = doesRecordWithSameTypeExist(record, newRecordSet)
|
||||||
|
doesNotExist = if(isRecordAlreadyExist) false else true
|
||||||
|
} yield doesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a record with same type already exist in 'recordset' mysql table
|
||||||
|
def doesRecordWithSameTypeExist(oldRecord: List[RecordSet], newRecord: RecordSet): Boolean = {
|
||||||
|
if(oldRecord.nonEmpty) {
|
||||||
|
val typeExists = oldRecord.map(x => x.typ == newRecord.typ)
|
||||||
|
if (typeExists.contains(true)) true else false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get zones that are allowed to create dotted hosts using the zones present in dotted hosts config
|
||||||
|
def getAllowedZones(zones: List[String]): IO[Set[String]] = {
|
||||||
|
if(zones.isEmpty){
|
||||||
|
val noZones: IO[Set[String]] = IO(Set.empty)
|
||||||
|
noZones
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Wildcard zones needs to be passed to a separate method
|
||||||
|
val wildcardZones = zones.filter(_.contains("*")).map(_.replace("*", "%"))
|
||||||
|
// Zones without wildcard character are passed to a separate function
|
||||||
|
val namedZones = zones.filter(zone => !zone.contains("*"))
|
||||||
|
for{
|
||||||
|
namedZoneResult <- zoneRepository.getZonesByNames(namedZones.toSet)
|
||||||
|
wildcardZoneResult <- zoneRepository.getZonesByFilters(wildcardZones.toSet)
|
||||||
|
zoneResult = namedZoneResult ++ wildcardZoneResult // Combine the zones
|
||||||
|
} yield zoneResult.map(x => x.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is allowed to create dotted hosts using the users present in dotted hosts config
|
||||||
|
def getAllowedDotsLimit(zone: Zone, config: DottedHostsConfig): Int = {
|
||||||
|
val configZones = config.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
val dottedZoneConfig = configZones.filter(_.contains("*")).map(_.replace("*", "[A-Za-z0-9.]*"))
|
||||||
|
val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.matches(x))
|
||||||
|
val isContainNormalZone = configZones.contains(zoneName)
|
||||||
|
if(isContainNormalZone){
|
||||||
|
config.zoneAuthConfigs.filter(x => x.zone == zoneName).head.dotsLimit
|
||||||
|
}
|
||||||
|
else if(isContainWildcardZone){
|
||||||
|
config.zoneAuthConfigs.filter(x => zoneName.matches(x.zone.replace("*", "[A-Za-z0-9.]*"))).head.dotsLimit
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is allowed to create dotted hosts using the users present in dotted hosts config
|
||||||
|
def checkIfInAllowedUsers(zone: Zone, config: DottedHostsConfig, auth: AuthPrincipal): Boolean = {
|
||||||
|
val configZones = config.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
val dottedZoneConfig = configZones.filter(_.contains("*")).map(_.replace("*", "[A-Za-z0-9.]*"))
|
||||||
|
val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.matches(x))
|
||||||
|
val isContainNormalZone = configZones.contains(zoneName)
|
||||||
|
if(isContainNormalZone){
|
||||||
|
val users = config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone == zoneName) x.userList else List.empty
|
||||||
|
}
|
||||||
|
if(users.contains(auth.signedInUser.userName)){
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isContainWildcardZone){
|
||||||
|
val users = config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone.contains("*")) {
|
||||||
|
val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*")
|
||||||
|
if (zoneName.matches(wildcardZone)) x.userList else List.empty
|
||||||
|
} else List.empty
|
||||||
|
}
|
||||||
|
if(users.contains(auth.signedInUser.userName)){
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is allowed to create dotted hosts using the record types present in dotted hosts config
|
||||||
|
def checkIfInAllowedRecordType(zone: Zone, config: DottedHostsConfig, rs: RecordSet): Boolean = {
|
||||||
|
val configZones = config.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
val dottedZoneConfig = configZones.filter(_.contains("*")).map(_.replace("*", "[A-Za-z0-9.]*"))
|
||||||
|
val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.matches(x))
|
||||||
|
val isContainNormalZone = configZones.contains(zoneName)
|
||||||
|
if(isContainNormalZone){
|
||||||
|
val rType = config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone == zoneName) x.recordTypes else List.empty
|
||||||
|
}
|
||||||
|
if(rType.contains(rs.typ.toString)){
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isContainWildcardZone){
|
||||||
|
val rType = config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone.contains("*")) {
|
||||||
|
val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*")
|
||||||
|
if (zoneName.matches(wildcardZone)) x.recordTypes else List.empty
|
||||||
|
} else List.empty
|
||||||
|
}
|
||||||
|
if(rType.contains(rs.typ.toString)){
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is allowed to create dotted hosts using the groups present in dotted hosts config
|
||||||
|
def checkIfInAllowedGroups(zone: Zone, config: DottedHostsConfig, auth: AuthPrincipal): IO[Boolean] = {
|
||||||
|
val configZones = config.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
val dottedZoneConfig = configZones.filter(_.contains("*")).map(_.replace("*", "[A-Za-z0-9.]*"))
|
||||||
|
val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.matches(x))
|
||||||
|
val isContainNormalZone = configZones.contains(zoneName)
|
||||||
|
val groups = if(isContainNormalZone){
|
||||||
|
config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone == zoneName) x.groupList else List.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isContainWildcardZone){
|
||||||
|
config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone.contains("*")) {
|
||||||
|
val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*")
|
||||||
|
if (zoneName.matches(wildcardZone)) x.groupList else List.empty
|
||||||
|
} else List.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
List.empty
|
||||||
|
}
|
||||||
|
for{
|
||||||
|
groupsInConfig <- groupRepository.getGroupsByName(groups.toSet)
|
||||||
|
members = groupsInConfig.flatMap(x => x.memberIds)
|
||||||
|
usersList <- if(members.isEmpty) IO(Seq.empty) else userRepository.getUsers(members, None, None).map(x => x.users)
|
||||||
|
users = if(usersList.isEmpty) Seq.empty else usersList.map(x => x.userName)
|
||||||
|
isPresent = users.contains(auth.signedInUser.userName)
|
||||||
|
} yield isPresent
|
||||||
|
}
|
||||||
|
|
||||||
def getRecordSet(
|
def getRecordSet(
|
||||||
recordSetId: String,
|
recordSetId: String,
|
||||||
authPrincipal: AuthPrincipal
|
authPrincipal: AuthPrincipal
|
||||||
|
@ -26,7 +26,7 @@ import vinyldns.core.domain.record.RecordType._
|
|||||||
import vinyldns.api.domain.zone._
|
import vinyldns.api.domain.zone._
|
||||||
import vinyldns.core.domain.auth.AuthPrincipal
|
import vinyldns.core.domain.auth.AuthPrincipal
|
||||||
import vinyldns.core.domain.membership.Group
|
import vinyldns.core.domain.membership.Group
|
||||||
import vinyldns.core.domain.record.{RecordType, RecordSet}
|
import vinyldns.core.domain.record.{RecordSet, RecordType}
|
||||||
import vinyldns.core.domain.zone.Zone
|
import vinyldns.core.domain.zone.Zone
|
||||||
import vinyldns.core.Messages._
|
import vinyldns.core.Messages._
|
||||||
|
|
||||||
@ -90,6 +90,69 @@ object RecordSetValidations {
|
|||||||
!existingRecordsWithName.exists(rs => rs.id != newRecordSet.id && rs.typ == newRecordSet.typ)
|
!existingRecordsWithName.exists(rs => rs.id != newRecordSet.id && rs.typ == newRecordSet.typ)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check whether the record has dot or not
|
||||||
|
def checkForDot(
|
||||||
|
newRecordSet: RecordSet,
|
||||||
|
zone: Zone,
|
||||||
|
existingRecordSet: Option[RecordSet] = None,
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
dottedHostZoneConfig: Set[String],
|
||||||
|
isRecordTypeAndUserAllowed: Boolean,
|
||||||
|
allowedDotsLimit: Int = 0
|
||||||
|
): Either[Throwable, Unit] = {
|
||||||
|
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
// Check if the zone of the recordset is present in dotted hosts config list
|
||||||
|
val isDomainAllowed = dottedHostZoneConfig.contains(zoneName)
|
||||||
|
|
||||||
|
// Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config
|
||||||
|
if(allowedDotsLimit != 0 && newRecordSet.name.contains(".") && isDomainAllowed && newRecordSet.name != zone.name) {
|
||||||
|
if(!isRecordTypeAndUserAllowed){
|
||||||
|
isUserAndRecordTypeAuthorized(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, isRecordTypeAndUserAllowed)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isDotted(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, isRecordTypeAndUserAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isNotDotted(newRecordSet, zone, existingRecordSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For dotted host. Check if a record is already present which conflicts with the new dotted record. If so, throw an error
|
||||||
|
def isDotted(
|
||||||
|
newRecordSet: RecordSet,
|
||||||
|
zone: Zone,
|
||||||
|
existingRecordSet: Option[RecordSet] = None,
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
isRecordTypeAndUserAllowed: Boolean
|
||||||
|
): Either[Throwable, Unit] =
|
||||||
|
ensuring(
|
||||||
|
InvalidRequest(
|
||||||
|
s"Record with fqdn '${newRecordSet.name}.${zone.name}' cannot be created. " +
|
||||||
|
s"Please check if a record with the same FQDN and type already exist and make the change there."
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
(newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && recordFqdnDoesNotExist && isRecordTypeAndUserAllowed
|
||||||
|
)
|
||||||
|
|
||||||
|
// For dotted host. Check if the user is authorized and the record type is allowed. If not, throw an error
|
||||||
|
def isUserAndRecordTypeAuthorized(
|
||||||
|
newRecordSet: RecordSet,
|
||||||
|
zone: Zone,
|
||||||
|
existingRecordSet: Option[RecordSet] = None,
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
isRecordTypeAndUserAllowed: Boolean
|
||||||
|
): Either[Throwable, Unit] =
|
||||||
|
ensuring(
|
||||||
|
InvalidRequest(
|
||||||
|
s"Record type is not allowed or the user is not authorized to create a dotted host in the zone '${zone.name}'"
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
(newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && recordFqdnDoesNotExist && isRecordTypeAndUserAllowed
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the recordset contains dot but is not in the allowed zones to create dotted records. If so, throw an error
|
||||||
def isNotDotted(
|
def isNotDotted(
|
||||||
newRecordSet: RecordSet,
|
newRecordSet: RecordSet,
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
@ -110,16 +173,20 @@ object RecordSetValidations {
|
|||||||
existingRecordsWithName: List[RecordSet],
|
existingRecordsWithName: List[RecordSet],
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
existingRecordSet: Option[RecordSet],
|
existingRecordSet: Option[RecordSet],
|
||||||
approvedNameServers: List[Regex]
|
approvedNameServers: List[Regex],
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
dottedHostZoneConfig: Set[String],
|
||||||
|
isRecordTypeAndUserAllowed: Boolean,
|
||||||
|
allowedDotsLimit: Int = 0
|
||||||
): Either[Throwable, Unit] =
|
): Either[Throwable, Unit] =
|
||||||
newRecordSet.typ match {
|
newRecordSet.typ match {
|
||||||
case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet)
|
case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers)
|
case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
case SOA => soaValidations(newRecordSet, zone)
|
case SOA => soaValidations(newRecordSet, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
case PTR => ptrValidations(newRecordSet, zone)
|
case PTR => ptrValidations(newRecordSet, zone)
|
||||||
case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR 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, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
case _ => isNotDotted(newRecordSet, zone, existingRecordSet)
|
case _ => checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] =
|
def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] =
|
||||||
@ -140,7 +207,11 @@ object RecordSetValidations {
|
|||||||
newRecordSet: RecordSet,
|
newRecordSet: RecordSet,
|
||||||
existingRecordsWithName: List[RecordSet],
|
existingRecordsWithName: List[RecordSet],
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
existingRecordSet: Option[RecordSet] = None
|
existingRecordSet: Option[RecordSet] = None,
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
dottedHostZoneConfig: Set[String],
|
||||||
|
isRecordTypeAndUserAllowed: Boolean,
|
||||||
|
allowedDotsLimit: Int = 0
|
||||||
): Either[Throwable, Unit] = {
|
): Either[Throwable, Unit] = {
|
||||||
// cannot create a cname record if a record with the same exists
|
// cannot create a cname record if a record with the same exists
|
||||||
val noRecordWithName = {
|
val noRecordWithName = {
|
||||||
@ -173,7 +244,7 @@ object RecordSetValidations {
|
|||||||
)
|
)
|
||||||
_ <- noRecordWithName
|
_ <- noRecordWithName
|
||||||
_ <- RDataWithConsecutiveDots
|
_ <- RDataWithConsecutiveDots
|
||||||
_ <- isNotDotted(newRecordSet, zone, existingRecordSet)
|
_ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -181,7 +252,11 @@ object RecordSetValidations {
|
|||||||
def dsValidations(
|
def dsValidations(
|
||||||
newRecordSet: RecordSet,
|
newRecordSet: RecordSet,
|
||||||
existingRecordsWithName: List[RecordSet],
|
existingRecordsWithName: List[RecordSet],
|
||||||
zone: Zone
|
zone: Zone,
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
dottedHostZoneConfig: Set[String],
|
||||||
|
isRecordTypeAndUserAllowed: Boolean,
|
||||||
|
allowedDotsLimit: Int = 0
|
||||||
): Either[Throwable, Unit] = {
|
): Either[Throwable, Unit] = {
|
||||||
// see https://tools.ietf.org/html/rfc4035#section-2.4
|
// see https://tools.ietf.org/html/rfc4035#section-2.4
|
||||||
val nsChecks = existingRecordsWithName.find(_.typ == NS) match {
|
val nsChecks = existingRecordsWithName.find(_.typ == NS) match {
|
||||||
@ -194,7 +269,7 @@ object RecordSetValidations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- isNotDotted(newRecordSet, zone)
|
_ <- checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
_ <- isNotOrigin(
|
_ <- isNotOrigin(
|
||||||
newRecordSet,
|
newRecordSet,
|
||||||
zone,
|
zone,
|
||||||
@ -208,10 +283,14 @@ object RecordSetValidations {
|
|||||||
newRecordSet: RecordSet,
|
newRecordSet: RecordSet,
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
oldRecordSet: Option[RecordSet],
|
oldRecordSet: Option[RecordSet],
|
||||||
approvedNameServers: List[Regex]
|
approvedNameServers: List[Regex],
|
||||||
|
recordFqdnDoesNotExist: Boolean,
|
||||||
|
dottedHostZoneConfig: Set[String],
|
||||||
|
isRecordTypeAndUserAllowed: Boolean,
|
||||||
|
allowedDotsLimit: Int = 0
|
||||||
): Either[Throwable, Unit] = {
|
): Either[Throwable, Unit] = {
|
||||||
// TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically
|
// TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically
|
||||||
val isNotDottedHost = if (!zone.isReverse) isNotDotted(newRecordSet, zone) else ().asRight
|
val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) else ().asRight
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- isNotDottedHost
|
_ <- isNotDottedHost
|
||||||
@ -233,9 +312,9 @@ object RecordSetValidations {
|
|||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
|
||||||
def soaValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] =
|
def soaValidations(newRecordSet: RecordSet, zone: Zone, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean, allowedDotsLimit: Int = 0): Either[Throwable, Unit] =
|
||||||
// TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here
|
// TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here
|
||||||
if (!zone.isReverse) isNotDotted(newRecordSet, zone) else ().asRight
|
if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) else ().asRight
|
||||||
|
|
||||||
def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] =
|
def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] =
|
||||||
// TODO we don't check for PTR as dotted...not sure why
|
// TODO we don't check for PTR as dotted...not sure why
|
||||||
@ -278,6 +357,29 @@ object RecordSetValidations {
|
|||||||
.leftMap(errors => InvalidRequest(errors.toList.map(_.message).mkString(", ")))
|
.leftMap(errors => InvalidRequest(errors.toList.map(_.message).mkString(", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def checkAllowedDots(allowedDotsLimit: Int, recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = {
|
||||||
|
ensuring(
|
||||||
|
InvalidRequest(
|
||||||
|
s"RecordSet with name ${recordSet.name} has more dots than that is allowed in config for this zone " +
|
||||||
|
s"which is, 'dots-limit = $allowedDotsLimit'."
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
recordSet.name.count(_ == '.') <= allowedDotsLimit || (recordSet.name.count(_ == '.') == 1 &&
|
||||||
|
recordSet.name.takeRight(1) == ".") || recordSet.name == zone.name ||
|
||||||
|
(recordSet.typ.toString == "PTR" || recordSet.typ.toString == "SRV" || recordSet.typ.toString == "TXT" || recordSet.typ.toString == "NAPTR")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def isNotApexEndsWithDot(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = {
|
||||||
|
ensuring(
|
||||||
|
InvalidRequest(
|
||||||
|
"RecordSet name cannot end with a dot, unless it's an apex record."
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
recordSet.name.endsWith(zone.name) || !recordSet.name.endsWith(".")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def canUseOwnerGroup(
|
def canUseOwnerGroup(
|
||||||
ownerGroupId: Option[String],
|
ownerGroupId: Option[String],
|
||||||
group: Option[Group],
|
group: Option[Group],
|
||||||
|
@ -508,9 +508,11 @@ def test_create_invalid_record_data(shared_zone_test_context):
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def test_create_dotted_a_record_not_apex_fails(shared_zone_test_context):
|
def test_create_dotted_a_record_not_apex_fails_when_dotted_hosts_config_not_satisfied(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
Test that creating a dotted host name A record set fails.
|
Test that creating a dotted host name A record set fails
|
||||||
|
Here the zone and user (individual) is allowed but record type is not allowed. Hence the test fails
|
||||||
|
Config present in reference.conf
|
||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
|
||||||
@ -524,8 +526,57 @@ def test_create_dotted_a_record_not_apex_fails(shared_zone_test_context):
|
|||||||
|
|
||||||
zone_name = shared_zone_test_context.parent_zone["name"]
|
zone_name = shared_zone_test_context.parent_zone["name"]
|
||||||
error = client.create_recordset(dotted_host_a_record, status=422)
|
error = client.create_recordset(dotted_host_a_record, status=422)
|
||||||
assert_that(error, is_("Record with name " + dotted_host_a_record["name"] + " and type A is a dotted host which "
|
assert_that(error, is_("Record type is not allowed or the user is not authorized to create a dotted host in the "
|
||||||
"is not allowed in zone " + zone_name))
|
"zone '" + zone_name + "'"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_dotted_a_record_succeeds_if_all_dotted_hosts_config_satisfied(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test that creating a A record set with dotted host record name succeeds
|
||||||
|
Here the zone, user (in group) and record type is allowed. Hence the test succeeds
|
||||||
|
Config present in reference.conf
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.history_client
|
||||||
|
zone = shared_zone_test_context.dummy_zone
|
||||||
|
dotted_host_a_record = {
|
||||||
|
"zoneId": zone["id"],
|
||||||
|
"name": "dot.ted",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 500,
|
||||||
|
"records": [{"address": "127.0.0.1"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
dotted_a_record = None
|
||||||
|
try:
|
||||||
|
dotted_cname_response = client.create_recordset(dotted_host_a_record, status=202)
|
||||||
|
dotted_a_record = client.wait_until_recordset_change_status(dotted_cname_response, "Complete")["recordSet"]
|
||||||
|
assert_that(dotted_a_record["name"], is_(dotted_host_a_record["name"]))
|
||||||
|
finally:
|
||||||
|
if dotted_a_record:
|
||||||
|
delete_result = client.delete_recordset(dotted_a_record["zoneId"], dotted_a_record["id"], status=202)
|
||||||
|
client.wait_until_recordset_change_status(delete_result, "Complete")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_dotted_a_record_fails_if_all_dotted_hosts_config_not_satisfied(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test that creating a A record set with dotted host record name fails
|
||||||
|
Here the zone, user (in group) and record type is allowed.
|
||||||
|
But the record name has more dots than the number of dots allowed for this zone. Hence the test fails
|
||||||
|
The 'dots-limit' config from dotted-hosts config is not satisfied. Config present in reference.conf
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.history_client
|
||||||
|
zone = shared_zone_test_context.dummy_zone
|
||||||
|
dotted_host_a_record = {
|
||||||
|
"zoneId": zone["id"],
|
||||||
|
"name": "dot.ted.trial.test.host",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 500,
|
||||||
|
"records": [{"address": "127.0.0.1"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
error = client.create_recordset(dotted_host_a_record, status=422)
|
||||||
|
assert_that(error, is_("RecordSet with name " + dotted_host_a_record["name"] + " has more dots than that is "
|
||||||
|
"allowed in config for this zone which is, 'dots-limit = 3'."))
|
||||||
|
|
||||||
|
|
||||||
def test_create_dotted_a_record_apex_succeeds(shared_zone_test_context):
|
def test_create_dotted_a_record_apex_succeeds(shared_zone_test_context):
|
||||||
@ -581,13 +632,15 @@ def test_create_dotted_a_record_apex_with_trailing_dot_succeeds(shared_zone_test
|
|||||||
client.wait_until_recordset_change_status(delete_result, "Complete")
|
client.wait_until_recordset_change_status(delete_result, "Complete")
|
||||||
|
|
||||||
|
|
||||||
def test_create_dotted_cname_record_fails(shared_zone_test_context):
|
def test_create_dotted_cname_record_fails_when_dotted_hosts_config_not_satisfied(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
Test that creating a CNAME record set with dotted host record name returns an error.
|
Test that creating a CNAME record set with dotted host record name returns an error
|
||||||
|
Here the zone is allowed but user (individual or in group) and record type is not allowed. Hence the test fails
|
||||||
|
Config present in reference.conf
|
||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.dummy_vinyldns_client
|
||||||
zone = shared_zone_test_context.parent_zone
|
zone = shared_zone_test_context.dummy_zone
|
||||||
apex_cname_rs = {
|
dotted_host_cname_record = {
|
||||||
"zoneId": zone["id"],
|
"zoneId": zone["id"],
|
||||||
"name": "dot.ted",
|
"name": "dot.ted",
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
@ -595,8 +648,37 @@ def test_create_dotted_cname_record_fails(shared_zone_test_context):
|
|||||||
"records": [{"cname": "foo.bar."}]
|
"records": [{"cname": "foo.bar."}]
|
||||||
}
|
}
|
||||||
|
|
||||||
error = client.create_recordset(apex_cname_rs, status=422)
|
error = client.create_recordset(dotted_host_cname_record, status=422)
|
||||||
assert_that(error, is_(f'Record with name dot.ted and type CNAME is a dotted host which is not allowed in zone {zone["name"]}'))
|
assert_that(error, is_("Record type is not allowed or the user is not authorized to create a dotted host in the "
|
||||||
|
"zone '" + zone["name"] + "'"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfied(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test that creating a CNAME record set with dotted host record name succeeds.
|
||||||
|
Here the zone, user (individual) and record type is allowed. Hence the test succeeds
|
||||||
|
Config present in reference.conf
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
zone = shared_zone_test_context.parent_zone
|
||||||
|
dotted_host_cname_record = {
|
||||||
|
"zoneId": zone["id"],
|
||||||
|
"name": "dot.ted",
|
||||||
|
"type": "CNAME",
|
||||||
|
"ttl": 500,
|
||||||
|
"records": [{"cname": "foo.bar."}]
|
||||||
|
}
|
||||||
|
|
||||||
|
dotted_cname_record = None
|
||||||
|
try:
|
||||||
|
dotted_cname_response = client.create_recordset(dotted_host_cname_record, status=202)
|
||||||
|
dotted_cname_record = client.wait_until_recordset_change_status(dotted_cname_response, "Complete")["recordSet"]
|
||||||
|
assert_that(dotted_cname_record["name"], is_(dotted_host_cname_record["name"]))
|
||||||
|
finally:
|
||||||
|
if dotted_cname_record:
|
||||||
|
delete_result = client.delete_recordset(dotted_cname_record["zoneId"], dotted_cname_record["id"],
|
||||||
|
status=202)
|
||||||
|
client.wait_until_recordset_change_status(delete_result, "Complete")
|
||||||
|
|
||||||
|
|
||||||
def test_create_cname_with_multiple_records(shared_zone_test_context):
|
def test_create_cname_with_multiple_records(shared_zone_test_context):
|
||||||
@ -701,7 +783,8 @@ def test_create_cname_with_existing_record_with_name_fails(shared_zone_test_cont
|
|||||||
a_record = client.wait_until_recordset_change_status(a_create, "Complete")["recordSet"]
|
a_record = client.wait_until_recordset_change_status(a_create, "Complete")["recordSet"]
|
||||||
|
|
||||||
error = client.create_recordset(cname_rs, status=409)
|
error = client.create_recordset(cname_rs, status=409)
|
||||||
assert_that(error, is_(f'RecordSet with name duplicate-test-name already exists in zone {zone["name"]}, CNAME record cannot use duplicate name'))
|
assert_that(error,
|
||||||
|
is_(f'RecordSet with name duplicate-test-name already exists in zone {zone["name"]}, CNAME record cannot use duplicate name'))
|
||||||
finally:
|
finally:
|
||||||
if a_record:
|
if a_record:
|
||||||
delete_result = client.delete_recordset(a_record["zoneId"], a_record["id"], status=202)
|
delete_result = client.delete_recordset(a_record["zoneId"], a_record["id"], status=202)
|
||||||
@ -744,7 +827,8 @@ def test_create_record_with_existing_cname_fails(shared_zone_test_context):
|
|||||||
cname_record = client.wait_until_recordset_change_status(cname_create, "Complete")["recordSet"]
|
cname_record = client.wait_until_recordset_change_status(cname_create, "Complete")["recordSet"]
|
||||||
|
|
||||||
error = client.create_recordset(a_rs, status=409)
|
error = client.create_recordset(a_rs, status=409)
|
||||||
assert_that(error, is_(f'RecordSet with name duplicate-test-name and type CNAME already exists in zone {zone["name"]}'))
|
assert_that(error,
|
||||||
|
is_(f'RecordSet with name duplicate-test-name and type CNAME already exists in zone {zone["name"]}'))
|
||||||
finally:
|
finally:
|
||||||
if cname_record:
|
if cname_record:
|
||||||
delete_result = client.delete_recordset(cname_record["zoneId"], cname_record["id"], status=202)
|
delete_result = client.delete_recordset(cname_record["zoneId"], cname_record["id"], status=202)
|
||||||
@ -1368,7 +1452,6 @@ def test_at_create_recordset(shared_zone_test_context):
|
|||||||
}
|
}
|
||||||
result = client.create_recordset(new_rs, status=202)
|
result = client.create_recordset(new_rs, status=202)
|
||||||
|
|
||||||
|
|
||||||
assert_that(result["changeType"], is_("Create"))
|
assert_that(result["changeType"], is_("Create"))
|
||||||
assert_that(result["status"], is_("Pending"))
|
assert_that(result["status"], is_("Pending"))
|
||||||
assert_that(result["created"], is_not(none()))
|
assert_that(result["created"], is_not(none()))
|
||||||
@ -1418,7 +1501,6 @@ def test_create_record_with_escape_characters_in_record_data_succeeds(shared_zon
|
|||||||
}
|
}
|
||||||
result = client.create_recordset(new_rs, status=202)
|
result = client.create_recordset(new_rs, status=202)
|
||||||
|
|
||||||
|
|
||||||
assert_that(result["changeType"], is_("Create"))
|
assert_that(result["changeType"], is_("Create"))
|
||||||
assert_that(result["status"], is_("Pending"))
|
assert_that(result["status"], is_("Pending"))
|
||||||
assert_that(result["created"], is_not(none()))
|
assert_that(result["created"], is_not(none()))
|
||||||
@ -1743,7 +1825,8 @@ def test_create_high_value_domain_fails(shared_zone_test_context):
|
|||||||
}
|
}
|
||||||
|
|
||||||
error = client.create_recordset(new_rs, status=422)
|
error = client.create_recordset(new_rs, status=422)
|
||||||
assert_that(error, is_(f'Record name "high-value-domain.{zone["name"]}" is configured as a High Value Domain, so it cannot be modified.'))
|
assert_that(error,
|
||||||
|
is_(f'Record name "high-value-domain.{zone["name"]}" is configured as a High Value Domain, so it cannot be modified.'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_high_value_domain_fails_case_insensitive(shared_zone_test_context):
|
def test_create_high_value_domain_fails_case_insensitive(shared_zone_test_context):
|
||||||
@ -1765,7 +1848,8 @@ def test_create_high_value_domain_fails_case_insensitive(shared_zone_test_contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
error = client.create_recordset(new_rs, status=422)
|
error = client.create_recordset(new_rs, status=422)
|
||||||
assert_that(error, is_(f'Record name "hIgH-vAlUe-dOmAiN.{zone["name"]}" is configured as a High Value Domain, so it cannot be modified.'))
|
assert_that(error,
|
||||||
|
is_(f'Record name "hIgH-vAlUe-dOmAiN.{zone["name"]}" is configured as a High Value Domain, so it cannot be modified.'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_high_value_domain_fails_for_ip4_ptr(shared_zone_test_context):
|
def test_create_high_value_domain_fails_for_ip4_ptr(shared_zone_test_context):
|
||||||
@ -1786,7 +1870,8 @@ def test_create_high_value_domain_fails_for_ip4_ptr(shared_zone_test_context):
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_ptr = client.create_recordset(ptr, status=422)
|
error_ptr = client.create_recordset(ptr, status=422)
|
||||||
assert_that(error_ptr, is_(f'Record name "{shared_zone_test_context.ip4_classless_prefix}.252" is configured as a High Value Domain, so it cannot be modified.'))
|
assert_that(error_ptr,
|
||||||
|
is_(f'Record name "{shared_zone_test_context.ip4_classless_prefix}.252" is configured as a High Value Domain, so it cannot be modified.'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_high_value_domain_fails_for_ip6_ptr(shared_zone_test_context):
|
def test_create_high_value_domain_fails_for_ip6_ptr(shared_zone_test_context):
|
||||||
@ -1807,7 +1892,8 @@ def test_create_high_value_domain_fails_for_ip6_ptr(shared_zone_test_context):
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_ptr = client.create_recordset(ptr, status=422)
|
error_ptr = client.create_recordset(ptr, status=422)
|
||||||
assert_that(error_ptr, is_(f'Record name "{shared_zone_test_context.ip6_prefix}:0000:0000:0000:0000:ffff" is configured as a High Value Domain, so it cannot be modified.'))
|
assert_that(error_ptr,
|
||||||
|
is_(f'Record name "{shared_zone_test_context.ip6_prefix}:0000:0000:0000:0000:ffff" is configured as a High Value Domain, so it cannot be modified.'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_with_owner_group_in_private_zone_by_admin_passes(shared_zone_test_context):
|
def test_create_with_owner_group_in_private_zone_by_admin_passes(shared_zone_test_context):
|
||||||
@ -1874,7 +1960,8 @@ def test_create_with_owner_group_in_private_zone_by_acl_passes(shared_zone_test_
|
|||||||
finally:
|
finally:
|
||||||
clear_ok_acl_rules(shared_zone_test_context)
|
clear_ok_acl_rules(shared_zone_test_context)
|
||||||
if create_rs:
|
if create_rs:
|
||||||
delete_result = shared_zone_test_context.ok_vinyldns_client.delete_recordset(zone["id"], create_rs["id"], status=202)
|
delete_result = shared_zone_test_context.ok_vinyldns_client.delete_recordset(zone["id"], create_rs["id"],
|
||||||
|
status=202)
|
||||||
shared_zone_test_context.ok_vinyldns_client.wait_until_recordset_change_status(delete_result, "Complete")
|
shared_zone_test_context.ok_vinyldns_client.wait_until_recordset_change_status(delete_result, "Complete")
|
||||||
|
|
||||||
|
|
||||||
@ -1900,8 +1987,11 @@ def test_create_with_owner_group_in_shared_zone_by_acl_passes(shared_zone_test_c
|
|||||||
finally:
|
finally:
|
||||||
clear_shared_zone_acl_rules(shared_zone_test_context)
|
clear_shared_zone_acl_rules(shared_zone_test_context)
|
||||||
if create_rs:
|
if create_rs:
|
||||||
delete_result = shared_zone_test_context.shared_zone_vinyldns_client.delete_recordset(zone["id"], create_rs["id"], status=202)
|
delete_result = shared_zone_test_context.shared_zone_vinyldns_client.delete_recordset(zone["id"],
|
||||||
shared_zone_test_context.shared_zone_vinyldns_client.wait_until_recordset_change_status(delete_result, "Complete")
|
create_rs["id"],
|
||||||
|
status=202)
|
||||||
|
shared_zone_test_context.shared_zone_vinyldns_client.wait_until_recordset_change_status(delete_result,
|
||||||
|
"Complete")
|
||||||
|
|
||||||
|
|
||||||
def test_create_in_shared_zone_without_owner_group_id_succeeds(shared_zone_test_context):
|
def test_create_in_shared_zone_without_owner_group_id_succeeds(shared_zone_test_context):
|
||||||
@ -1955,10 +2045,12 @@ def test_create_in_shared_zone_by_unassociated_user_fails_if_record_type_is_not_
|
|||||||
zone = shared_zone_test_context.shared_zone
|
zone = shared_zone_test_context.shared_zone
|
||||||
group = shared_zone_test_context.dummy_group
|
group = shared_zone_test_context.dummy_group
|
||||||
|
|
||||||
record_json = create_recordset(zone, "test_shared_not_approved_record_type", "MX", [{"preference": 3, "exchange": "mx"}])
|
record_json = create_recordset(zone, "test_shared_not_approved_record_type", "MX",
|
||||||
|
[{"preference": 3, "exchange": "mx"}])
|
||||||
record_json["ownerGroupId"] = group["id"]
|
record_json["ownerGroupId"] = group["id"]
|
||||||
error = client.create_recordset(record_json, status=403)
|
error = client.create_recordset(record_json, status=403)
|
||||||
assert_that(error, is_(f'User dummy does not have access to create test-shared-not-approved-record-type.{zone["name"]}'))
|
assert_that(error,
|
||||||
|
is_(f'User dummy does not have access to create test-shared-not-approved-record-type.{zone["name"]}'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_with_not_found_owner_group_fails(shared_zone_test_context):
|
def test_create_with_not_found_owner_group_fails(shared_zone_test_context):
|
||||||
@ -1997,7 +2089,8 @@ def test_create_ds_success(shared_zone_test_context):
|
|||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [
|
record_data = [
|
||||||
{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"},
|
{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"},
|
||||||
{"keytag": 60485, "algorithm": 5, "digesttype": 2, "digest": "D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A"}
|
{"keytag": 60485, "algorithm": 5, "digesttype": 2,
|
||||||
|
"digest": "D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A"}
|
||||||
]
|
]
|
||||||
record_json = create_recordset(zone, "dskey", "DS", record_data, ttl=3600)
|
record_json = create_recordset(zone, "dskey", "DS", record_data, ttl=3600)
|
||||||
result_rs = None
|
result_rs = None
|
||||||
@ -2039,7 +2132,8 @@ def test_create_ds_unknown_algorithm(shared_zone_test_context):
|
|||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [{"keytag": 60485, "algorithm": 0, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
record_data = [
|
||||||
|
{"keytag": 60485, "algorithm": 0, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
||||||
record_json = create_recordset(zone, "dskey", "DS", record_data)
|
record_json = create_recordset(zone, "dskey", "DS", record_data)
|
||||||
errors = client.create_recordset(record_json, status=400)["errors"]
|
errors = client.create_recordset(record_json, status=400)["errors"]
|
||||||
assert_that(errors, contains_inanyorder("Algorithm 0 is not a supported DNSSEC algorithm"))
|
assert_that(errors, contains_inanyorder("Algorithm 0 is not a supported DNSSEC algorithm"))
|
||||||
@ -2051,7 +2145,8 @@ def test_create_ds_unknown_digest_type(shared_zone_test_context):
|
|||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [{"keytag": 60485, "algorithm": 5, "digesttype": 0, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
record_data = [
|
||||||
|
{"keytag": 60485, "algorithm": 5, "digesttype": 0, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
||||||
record_json = create_recordset(zone, "dskey", "DS", record_data)
|
record_json = create_recordset(zone, "dskey", "DS", record_data)
|
||||||
errors = client.create_recordset(record_json, status=400)["errors"]
|
errors = client.create_recordset(record_json, status=400)["errors"]
|
||||||
assert_that(errors, contains_inanyorder("Digest Type 0 is not a supported DS record digest type"))
|
assert_that(errors, contains_inanyorder("Digest Type 0 is not a supported DS record digest type"))
|
||||||
@ -2063,10 +2158,12 @@ def test_create_ds_no_ns_fails(shared_zone_test_context):
|
|||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
record_data = [
|
||||||
|
{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
||||||
record_json = create_recordset(zone, "no-ns-exists", "DS", record_data, ttl=3600)
|
record_json = create_recordset(zone, "no-ns-exists", "DS", record_data, ttl=3600)
|
||||||
error = client.create_recordset(record_json, status=422)
|
error = client.create_recordset(record_json, status=422)
|
||||||
assert_that(error, is_(f'DS record [no-ns-exists] is invalid because there is no NS record with that name in the zone [{zone["name"]}]'))
|
assert_that(error,
|
||||||
|
is_(f'DS record [no-ns-exists] is invalid because there is no NS record with that name in the zone [{zone["name"]}]'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_apex_ds_fails(shared_zone_test_context):
|
def test_create_apex_ds_fails(shared_zone_test_context):
|
||||||
@ -2075,7 +2172,8 @@ def test_create_apex_ds_fails(shared_zone_test_context):
|
|||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
record_data = [
|
||||||
|
{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
||||||
record_json = create_recordset(zone, "@", "DS", record_data, ttl=100)
|
record_json = create_recordset(zone, "@", "DS", record_data, ttl=100)
|
||||||
error = client.create_recordset(record_json, status=422)
|
error = client.create_recordset(record_json, status=422)
|
||||||
assert_that(error, is_(f'Record with name [{zone["name"]}] is an DS record at apex and cannot be added'))
|
assert_that(error, is_(f'Record with name [{zone["name"]}] is an DS record at apex and cannot be added'))
|
||||||
@ -2087,7 +2185,9 @@ def test_create_dotted_ds_fails(shared_zone_test_context):
|
|||||||
"""
|
"""
|
||||||
client = shared_zone_test_context.ok_vinyldns_client
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
zone = shared_zone_test_context.ds_zone
|
zone = shared_zone_test_context.ds_zone
|
||||||
record_data = [{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
record_data = [
|
||||||
|
{"keytag": 60485, "algorithm": 5, "digesttype": 1, "digest": "2BB183AF5F22588179A53B0A98631FAD1A292118"}]
|
||||||
record_json = create_recordset(zone, "dotted.ds", "DS", record_data, ttl=100)
|
record_json = create_recordset(zone, "dotted.ds", "DS", record_data, ttl=100)
|
||||||
error = client.create_recordset(record_json, status=422)
|
error = client.create_recordset(record_json, status=422)
|
||||||
assert_that(error, is_(f'Record with name dotted.ds and type DS is a dotted host which is not allowed in zone {zone["name"]}'))
|
assert_that(error,
|
||||||
|
is_(f'Record with name dotted.ds and type DS is a dotted host which is not allowed in zone {zone["name"]}'))
|
||||||
|
@ -174,6 +174,15 @@ class SharedZoneTestContext(object):
|
|||||||
"shared": False,
|
"shared": False,
|
||||||
"adminGroupId": self.dummy_group["id"],
|
"adminGroupId": self.dummy_group["id"],
|
||||||
"isTest": True,
|
"isTest": True,
|
||||||
|
"acl": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"accessLevel": "Delete",
|
||||||
|
"description": "some_test_rule",
|
||||||
|
"userId": "history-id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"connection": {
|
"connection": {
|
||||||
"name": "dummy.",
|
"name": "dummy.",
|
||||||
"keyName": VinylDNSTestContext.dns_key_name,
|
"keyName": VinylDNSTestContext.dns_key_name,
|
||||||
|
@ -18,7 +18,7 @@ package vinyldns.api
|
|||||||
|
|
||||||
import com.comcast.ip4s.IpAddress
|
import com.comcast.ip4s.IpAddress
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import vinyldns.api.config.{BatchChangeConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig}
|
import vinyldns.api.config.{ZoneAuthConfigs, BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig}
|
||||||
import vinyldns.api.domain.batch.V6DiscoveryNibbleBoundaries
|
import vinyldns.api.domain.batch.V6DiscoveryNibbleBoundaries
|
||||||
import vinyldns.core.domain.record._
|
import vinyldns.core.domain.record._
|
||||||
import vinyldns.core.domain.zone._
|
import vinyldns.core.domain.zone._
|
||||||
@ -40,6 +40,10 @@ trait VinylDNSTestHelpers {
|
|||||||
|
|
||||||
val approvedNameServers: List[Regex] = List(new Regex("some.test.ns."))
|
val approvedNameServers: List[Regex] = List(new Regex("some.test.ns."))
|
||||||
|
|
||||||
|
val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(ZoneAuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME"), 3), ZoneAuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME"), 3), ZoneAuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME"), 3), ZoneAuthConfigs("dot.xyz.",List("super"),List("xyz"),List("CNAME"), 0)))
|
||||||
|
|
||||||
|
val emptyDottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List.empty)
|
||||||
|
|
||||||
val defaultTtl: Long = 7200
|
val defaultTtl: Long = 7200
|
||||||
|
|
||||||
val manualReviewDomainList: List[Regex] = List(new Regex("needs-review.*"))
|
val manualReviewDomainList: List[Regex] = List(new Regex("needs-review.*"))
|
||||||
|
@ -55,7 +55,7 @@ import vinyldns.api.domain.access.AccessValidations
|
|||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
class BatchChangeServiceSpec
|
class BatchChangeServiceSpec
|
||||||
extends AnyWordSpec
|
extends AnyWordSpec
|
||||||
with Matchers
|
with Matchers
|
||||||
with MockitoSugar
|
with MockitoSugar
|
||||||
with CatsHelpers
|
with CatsHelpers
|
||||||
@ -2559,4 +2559,4 @@ class BatchChangeServiceSpec
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,7 +41,7 @@ import vinyldns.core.domain.zone.{ACLRule, AccessLevel, Zone, ZoneStatus}
|
|||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
class BatchChangeValidationsSpec
|
class BatchChangeValidationsSpec
|
||||||
extends AnyPropSpec
|
extends AnyPropSpec
|
||||||
with Matchers
|
with Matchers
|
||||||
with ScalaCheckDrivenPropertyChecks
|
with ScalaCheckDrivenPropertyChecks
|
||||||
with EitherMatchers
|
with EitherMatchers
|
||||||
@ -192,9 +192,9 @@ class BatchChangeValidationsSpec
|
|||||||
)
|
)
|
||||||
|
|
||||||
private def makeAddUpdateRecord(
|
private def makeAddUpdateRecord(
|
||||||
recordName: String,
|
recordName: String,
|
||||||
aData: AData = AData("1.2.3.4")
|
aData: AData = AData("1.2.3.4")
|
||||||
): AddChangeForValidation =
|
): AddChangeForValidation =
|
||||||
AddChangeForValidation(
|
AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
s"$recordName",
|
s"$recordName",
|
||||||
@ -203,9 +203,9 @@ class BatchChangeValidationsSpec
|
|||||||
)
|
)
|
||||||
|
|
||||||
private def makeDeleteUpdateDeleteRRSet(
|
private def makeDeleteUpdateDeleteRRSet(
|
||||||
recordName: String,
|
recordName: String,
|
||||||
recordData: Option[RecordData] = None
|
recordData: Option[RecordData] = None
|
||||||
): DeleteRRSetChangeForValidation =
|
): DeleteRRSetChangeForValidation =
|
||||||
DeleteRRSetChangeForValidation(
|
DeleteRRSetChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
s"$recordName",
|
s"$recordName",
|
||||||
@ -666,7 +666,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateAddChangeInput: should fail with InvalidIpv4Address
|
property("""validateAddChangeInput: should fail with InvalidIpv4Address
|
||||||
|if validateRecordData fails for an invalid ipv4 address""".stripMargin) {
|
|if validateRecordData fails for an invalid ipv4 address""".stripMargin) {
|
||||||
val invalidIpv4 = "invalidIpv4:123"
|
val invalidIpv4 = "invalidIpv4:123"
|
||||||
val change = AddChangeInput("test.comcast.com.", RecordType.A, ttl, AData(invalidIpv4))
|
val change = AddChangeInput("test.comcast.com.", RecordType.A, ttl, AData(invalidIpv4))
|
||||||
val result = validateAddChangeInput(change, false)
|
val result = validateAddChangeInput(change, false)
|
||||||
@ -1038,7 +1038,7 @@ class BatchChangeValidationsSpec
|
|||||||
|
|
||||||
property(
|
property(
|
||||||
"""validateChangesWithContext: should succeed for update in shared zone if user belongs to record
|
"""validateChangesWithContext: should succeed for update in shared zone if user belongs to record
|
||||||
| owner group""".stripMargin
|
| owner group""".stripMargin
|
||||||
) {
|
) {
|
||||||
val existingRecord =
|
val existingRecord =
|
||||||
sharedZoneRecord.copy(
|
sharedZoneRecord.copy(
|
||||||
@ -1073,7 +1073,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should succeed adding a record
|
property("""validateChangesWithContext: should succeed adding a record
|
||||||
|if an existing CNAME with the same name exists but is being deleted""".stripMargin) {
|
|if an existing CNAME with the same name exists but is being deleted""".stripMargin) {
|
||||||
val existingCname = rsOk.copy(name = "deleteRRSet", typ = RecordType.CNAME)
|
val existingCname = rsOk.copy(name = "deleteRRSet", typ = RecordType.CNAME)
|
||||||
val existingCname2 =
|
val existingCname2 =
|
||||||
existingCname.copy(name = "deleteRecord", records = List(CNAMEData(Fqdn("cname.data."))))
|
existingCname.copy(name = "deleteRecord", records = List(CNAMEData(Fqdn("cname.data."))))
|
||||||
@ -1218,7 +1218,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should fail with CnameIsNotUniqueError
|
property("""validateChangesWithContext: should fail with CnameIsNotUniqueError
|
||||||
|if CNAME record name already exists""".stripMargin) {
|
|if CNAME record name already exists""".stripMargin) {
|
||||||
val addCname = AddChangeForValidation(
|
val addCname = AddChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
"existingCname",
|
"existingCname",
|
||||||
@ -1240,7 +1240,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should succeed for CNAME record
|
property("""validateChangesWithContext: should succeed for CNAME record
|
||||||
|if there's a duplicate PTR ipv4 record that is being deleted""".stripMargin) {
|
|if there's a duplicate PTR ipv4 record that is being deleted""".stripMargin) {
|
||||||
val addCname = AddChangeForValidation(
|
val addCname = AddChangeForValidation(
|
||||||
validIp4ReverseZone,
|
validIp4ReverseZone,
|
||||||
"30",
|
"30",
|
||||||
@ -1268,7 +1268,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should fail with CnameIsNotUniqueError for CNAME record
|
property("""validateChangesWithContext: should fail with CnameIsNotUniqueError for CNAME record
|
||||||
|if there's a duplicate PTR ipv6 record""".stripMargin) {
|
|if there's a duplicate PTR ipv6 record""".stripMargin) {
|
||||||
val addCname = AddChangeForValidation(
|
val addCname = AddChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
"0.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0",
|
"0.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0",
|
||||||
@ -1294,7 +1294,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: CNAME record should pass
|
property("""validateChangesWithContext: CNAME record should pass
|
||||||
|if no other changes in batch change have same record name""".stripMargin) {
|
|if no other changes in batch change have same record name""".stripMargin) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
"test",
|
"test",
|
||||||
@ -1329,7 +1329,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: CNAME record should fail
|
property("""validateChangesWithContext: CNAME record should fail
|
||||||
|if another add change in batch change has the same record name""".stripMargin) {
|
|if another add change in batch change has the same record name""".stripMargin) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
"test",
|
"test",
|
||||||
@ -1369,7 +1369,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: both CNAME records should fail
|
property("""validateChangesWithContext: both CNAME records should fail
|
||||||
|if there are duplicate CNAME add change inputs""".stripMargin) {
|
|if there are duplicate CNAME add change inputs""".stripMargin) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
"test",
|
"test",
|
||||||
@ -1411,7 +1411,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: both PTR records should succeed
|
property("""validateChangesWithContext: both PTR records should succeed
|
||||||
|if there are duplicate PTR add change inputs""".stripMargin) {
|
|if there are duplicate PTR add change inputs""".stripMargin) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
"test",
|
"test",
|
||||||
@ -1444,7 +1444,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should succeed for AddChangeForValidation
|
property("""validateChangesWithContext: should succeed for AddChangeForValidation
|
||||||
|if user has group admin access""".stripMargin) {
|
|if user has group admin access""".stripMargin) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
"valid",
|
"valid",
|
||||||
@ -1533,7 +1533,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should fail with RecordNameNotUniqueInBatch for PTR record
|
property("""validateChangesWithContext: should fail with RecordNameNotUniqueInBatch for PTR record
|
||||||
|if valid CNAME with same name exists in batch""".stripMargin) {
|
|if valid CNAME with same name exists in batch""".stripMargin) {
|
||||||
val addCname = AddChangeForValidation(
|
val addCname = AddChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
"existing",
|
"existing",
|
||||||
@ -1604,7 +1604,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should succeed for DeleteChangeForValidation
|
property("""validateChangesWithContext: should succeed for DeleteChangeForValidation
|
||||||
|if record set status is Active""".stripMargin) {
|
|if record set status is Active""".stripMargin) {
|
||||||
val deleteA = DeleteRRSetChangeForValidation(
|
val deleteA = DeleteRRSetChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
"Active-record-status",
|
"Active-record-status",
|
||||||
@ -1629,7 +1629,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should succeed for DeleteChangeForValidation
|
property("""validateChangesWithContext: should succeed for DeleteChangeForValidation
|
||||||
|if user has group admin access"""".stripMargin) {
|
|if user has group admin access"""".stripMargin) {
|
||||||
val deleteA =
|
val deleteA =
|
||||||
DeleteRRSetChangeForValidation(
|
DeleteRRSetChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
@ -1651,7 +1651,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property(""" validateChangesWithContext: should fail for DeleteChangeForValidation
|
property(""" validateChangesWithContext: should fail for DeleteChangeForValidation
|
||||||
| if user is superUser with no other access""".stripMargin) {
|
| if user is superUser with no other access""".stripMargin) {
|
||||||
val deleteA =
|
val deleteA =
|
||||||
DeleteRRSetChangeForValidation(
|
DeleteRRSetChangeForValidation(
|
||||||
validZone,
|
validZone,
|
||||||
@ -1734,7 +1734,7 @@ class BatchChangeValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("""validateChangesWithContext: should properly process batch that contains
|
property("""validateChangesWithContext: should properly process batch that contains
|
||||||
|a CNAME and different type record with the same name""".stripMargin) {
|
|a CNAME and different type record with the same name""".stripMargin) {
|
||||||
val addDuplicateA = AddChangeForValidation(
|
val addDuplicateA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
"test",
|
"test",
|
||||||
@ -2445,7 +2445,7 @@ class BatchChangeValidationsSpec
|
|||||||
|
|
||||||
property(
|
property(
|
||||||
"""validateChangesWithContext: should fail validateAddWithContext with
|
"""validateChangesWithContext: should fail validateAddWithContext with
|
||||||
|ZoneDiscoveryError if new record is dotted host but not a TXT record type""".stripMargin
|
|ZoneDiscoveryError if new record is dotted host but not a TXT record type""".stripMargin
|
||||||
) {
|
) {
|
||||||
val addA = AddChangeForValidation(
|
val addA = AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
|
@ -24,6 +24,7 @@ import org.scalatestplus.mockito.MockitoSugar
|
|||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
import org.scalatest.wordspec.AnyWordSpec
|
||||||
import org.scalatest.BeforeAndAfterEach
|
import org.scalatest.BeforeAndAfterEach
|
||||||
|
import vinyldns.api.config.{ZoneAuthConfigs, DottedHostsConfig}
|
||||||
import vinyldns.api.{ResultHelpers, VinylDNSTestHelpers}
|
import vinyldns.api.{ResultHelpers, VinylDNSTestHelpers}
|
||||||
import vinyldns.api.domain.access.AccessValidations
|
import vinyldns.api.domain.access.AccessValidations
|
||||||
import vinyldns.api.domain.record.RecordSetHelpers._
|
import vinyldns.api.domain.record.RecordSetHelpers._
|
||||||
@ -83,6 +84,7 @@ class RecordSetServiceSpec
|
|||||||
mockBackendResolver,
|
mockBackendResolver,
|
||||||
false,
|
false,
|
||||||
VinylDNSTestHelpers.highValueDomainConfig,
|
VinylDNSTestHelpers.highValueDomainConfig,
|
||||||
|
VinylDNSTestHelpers.dottedHostsConfig,
|
||||||
VinylDNSTestHelpers.approvedNameServers,
|
VinylDNSTestHelpers.approvedNameServers,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@ -101,10 +103,57 @@ class RecordSetServiceSpec
|
|||||||
mockBackendResolver,
|
mockBackendResolver,
|
||||||
true,
|
true,
|
||||||
VinylDNSTestHelpers.highValueDomainConfig,
|
VinylDNSTestHelpers.highValueDomainConfig,
|
||||||
|
VinylDNSTestHelpers.dottedHostsConfig,
|
||||||
VinylDNSTestHelpers.approvedNameServers,
|
VinylDNSTestHelpers.approvedNameServers,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val underTestWithEmptyDottedHostsConfig = new RecordSetService(
|
||||||
|
mockZoneRepo,
|
||||||
|
mockGroupRepo,
|
||||||
|
mockRecordRepo,
|
||||||
|
mockRecordDataRepo,
|
||||||
|
mockRecordChangeRepo,
|
||||||
|
mockUserRepo,
|
||||||
|
mockMessageQueue,
|
||||||
|
new AccessValidations(
|
||||||
|
sharedApprovedTypes = VinylDNSTestHelpers.sharedApprovedTypes
|
||||||
|
),
|
||||||
|
mockBackendResolver,
|
||||||
|
true,
|
||||||
|
VinylDNSTestHelpers.highValueDomainConfig,
|
||||||
|
VinylDNSTestHelpers.emptyDottedHostsConfig,
|
||||||
|
VinylDNSTestHelpers.approvedNameServers,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
def getDottedHostsConfigGroupsAllowed(zone: Zone, config: DottedHostsConfig): List[String] = {
|
||||||
|
val configZones = config.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name
|
||||||
|
val dottedZoneConfig = configZones.filter(_.contains("*")).map(_.replace("*", "[A-Za-z.]*"))
|
||||||
|
val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.substring(0, zoneName.length - 1).matches(x))
|
||||||
|
val isContainNormalZone = configZones.contains(zoneName)
|
||||||
|
val groups = if (isContainWildcardZone || isContainNormalZone) {
|
||||||
|
config.zoneAuthConfigs.flatMap {
|
||||||
|
x: ZoneAuthConfigs =>
|
||||||
|
if (x.zone.contains("*")) {
|
||||||
|
val wildcardZone = x.zone.replace("*", "[A-Za-z.]*")
|
||||||
|
if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.groupList else List.empty
|
||||||
|
} else {
|
||||||
|
if (x.zone == zoneName) x.groupList else List.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
List.empty
|
||||||
|
}
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(okZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
"addRecordSet" should {
|
"addRecordSet" should {
|
||||||
"return the recordSet change as the result" in {
|
"return the recordSet change as the result" in {
|
||||||
val record = aaaa.copy(zoneId = okZone.id)
|
val record = aaaa.copy(zoneId = okZone.id)
|
||||||
@ -115,6 +164,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange =
|
val result: RecordSetChange =
|
||||||
rightResultOf(
|
rightResultOf(
|
||||||
@ -132,7 +202,6 @@ class RecordSetServiceSpec
|
|||||||
val result = leftResultOf(underTest.getRecordSetByZone(aaaa.id, mockZone.id, okAuth).value)
|
val result = leftResultOf(underTest.getRecordSetByZone(aaaa.id, mockZone.id, okAuth).value)
|
||||||
result shouldBe a[ZoneNotFoundError]
|
result shouldBe a[ZoneNotFoundError]
|
||||||
}
|
}
|
||||||
|
|
||||||
"fail when the account is not authorized" in {
|
"fail when the account is not authorized" in {
|
||||||
doReturn(IO.pure(Some(aaaa)))
|
doReturn(IO.pure(Some(aaaa)))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
@ -155,7 +224,7 @@ class RecordSetServiceSpec
|
|||||||
val result = leftResultOf(underTest.addRecordSet(aaaa, okAuth).value)
|
val result = leftResultOf(underTest.addRecordSet(aaaa, okAuth).value)
|
||||||
result shouldBe a[RecordSetAlreadyExists]
|
result shouldBe a[RecordSetAlreadyExists]
|
||||||
}
|
}
|
||||||
"fail if the record is dotted" in {
|
"fail if the record is dotted and does not satisfy properties in dotted hosts config" in {
|
||||||
val record =
|
val record =
|
||||||
aaaa.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active)
|
aaaa.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
@ -165,10 +234,66 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result = leftResultOf(underTest.addRecordSet(record, okAuth).value)
|
val result = leftResultOf(underTest.addRecordSet(record, okAuth).value)
|
||||||
result shouldBe an[InvalidRequest]
|
result shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
"fail if the record is dotted and dotted hosts config is empty" in {
|
||||||
|
val record =
|
||||||
|
aaaa.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(okZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
|
val result = leftResultOf(underTestWithEmptyDottedHostsConfig.addRecordSet(record, okAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
"fail if the record is relative with trailing dot" in {
|
"fail if the record is relative with trailing dot" in {
|
||||||
val record =
|
val record =
|
||||||
aaaa.copy(name = "new.", zoneId = okZone.id, status = RecordSetStatus.Active)
|
aaaa.copy(name = "new.", zoneId = okZone.id, status = RecordSetStatus.Active)
|
||||||
@ -179,6 +304,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
leftResultOf(underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value)
|
leftResultOf(underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value)
|
||||||
@ -204,6 +350,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -222,6 +389,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -259,6 +447,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(Some(okGroup)))
|
doReturn(IO.pure(Some(okGroup)))
|
||||||
.when(mockGroupRepo)
|
.when(mockGroupRepo)
|
||||||
.getGroup(okGroup.id)
|
.getGroup(okGroup.id)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -312,6 +521,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, record.name)
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange =
|
val result: RecordSetChange =
|
||||||
rightResultOf(
|
rightResultOf(
|
||||||
@ -326,6 +556,296 @@ class RecordSetServiceSpec
|
|||||||
result.status shouldBe RecordSetChangeStatus.Pending
|
result.status shouldBe RecordSetChangeStatus.Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"succeed if the record is dotted and zone, user, record type is in allowed dotted hosts config" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(dottedZone))).when(mockZoneRepo).getZone(dottedZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(dottedZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(dottedZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + dottedZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + dottedZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + dottedZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(dummyGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(dummyGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// passes as all three properties within dotted hosts config (allowed zones, users and record types) are satisfied
|
||||||
|
val result: RecordSetChange = rightResultOf(
|
||||||
|
underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
|
)
|
||||||
|
|
||||||
|
result.recordSet.name shouldBe record.name
|
||||||
|
}
|
||||||
|
"succeed if the record is dotted and zone, user in group, record type is in allowed dotted hosts config" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name", zoneId = xyzZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(xyzZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(xyzZone))).when(mockZoneRepo).getZone(xyzZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(xyzZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(xyzZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(xyzZone, abcZone, xyzZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + xyzZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + xyzZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + xyzZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(xyzGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(xyzGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// passes as all three properties within dotted hosts config (allowed zones, users and record types) are satisfied
|
||||||
|
val result: RecordSetChange = rightResultOf(
|
||||||
|
underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
|
)
|
||||||
|
|
||||||
|
result.recordSet.name shouldBe record.name
|
||||||
|
}
|
||||||
|
"fail if the record is dotted and zone, user in group, record type is allowed but record name has dot in the end and is not an apex record" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name.", zoneId = xyzZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(xyzZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(xyzZone))).when(mockZoneRepo).getZone(xyzZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(xyzZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(xyzZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(xyzZone, abcZone, xyzZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + xyzZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + xyzZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + xyzZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(xyzGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(xyzGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// fails as dotted host record name has dot at the end and is not an apex record
|
||||||
|
val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
"fail if the record is dotted and zone, user, record type is allowed but number of dots allowed in config is 0" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name", zoneId = dotZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(dotZone))).when(mockZoneRepo).getZone(dotZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(dotZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(dotZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + dotZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + dotZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + dotZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(dummyGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(dummyGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// fails as no.of.dots allowed for the zone in the config is 0
|
||||||
|
val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
"fail if the record is dotted and user, record type is in allowed dotted hosts config except zone" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(okZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(okZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
|
// fails as only two properties within dotted hosts config (users and record types) are satisfied while zone is not allowed
|
||||||
|
val result = leftResultOf(underTest.addRecordSet(record, okAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
"fail if the record is dotted and zone, record type is in allowed dotted hosts config except user" in {
|
||||||
|
val record =
|
||||||
|
cname.copy(name = "new.name", zoneId = abcZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(abcZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(abcZone))).when(mockZoneRepo).getZone(abcZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(abcZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(abcZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(abcZone, dottedZone, xyzZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + abcZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + abcZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + abcZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(dummyGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(dummyGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// fails as only two properties within dotted hosts config (zones and record types) are satisfied while user is not allowed
|
||||||
|
val result = leftResultOf(underTest.addRecordSet(record, abcAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
"fail if the record is dotted and zone, user is in allowed dotted hosts config except record type" in {
|
||||||
|
val record =
|
||||||
|
aaaa.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active)
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map {
|
||||||
|
case y:ZoneAuthConfigs => y.zone
|
||||||
|
}
|
||||||
|
|
||||||
|
val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(dottedZone))).when(mockZoneRepo).getZone(dottedZone.id)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSets(dottedZone.id, record.name, record.typ)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByName(dottedZone.id, record.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(record.name + "." + dottedZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(record.name + "." + dottedZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(record.name.split('.').map(x => x + "." + dottedZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set(dummyGroup)))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(dummyGroup.memberIds, None, None)
|
||||||
|
|
||||||
|
// fails as only two properties within dotted hosts config (zone and user) are satisfied while record type is not allowed
|
||||||
|
val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value)
|
||||||
|
result shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
"updateRecordSet" should {
|
"updateRecordSet" should {
|
||||||
"return the recordSet change as the result" in {
|
"return the recordSet change as the result" in {
|
||||||
@ -341,6 +861,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -377,6 +918,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -416,6 +978,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -438,6 +1021,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -460,6 +1064,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List()))
|
doReturn(IO.pure(List()))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result: RecordSetChange = rightResultOf(
|
val result: RecordSetChange = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -595,6 +1220,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(Some(oneUserDummyGroup)))
|
doReturn(IO.pure(Some(oneUserDummyGroup)))
|
||||||
.when(mockGroupRepo)
|
.when(mockGroupRepo)
|
||||||
.getGroup(oneUserDummyGroup.id)
|
.getGroup(oneUserDummyGroup.id)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result = rightResultOf(
|
val result = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
@ -624,6 +1270,27 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(List(oldRecord)))
|
doReturn(IO.pure(List(oldRecord)))
|
||||||
.when(mockRecordRepo)
|
.when(mockRecordRepo)
|
||||||
.getRecordSetsByName(zone.id, newRecord.name)
|
.getRecordSetsByName(zone.id, newRecord.name)
|
||||||
|
doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone)))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByNames(dottedHostsConfigZonesAllowed.toSet)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZoneByName(newRecord.name + "." + okZone.name)
|
||||||
|
doReturn(IO.pure(List()))
|
||||||
|
.when(mockRecordRepo)
|
||||||
|
.getRecordSetsByFQDNs(Set(newRecord.name + "." + okZone.name))
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockZoneRepo)
|
||||||
|
.getZonesByFilters(Set.empty)
|
||||||
|
doReturn(IO.pure(Set()))
|
||||||
|
.when(mockGroupRepo)
|
||||||
|
.getGroupsByName(dottedHostsConfigGroupsAllowed.toSet)
|
||||||
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
|
.when(mockUserRepo)
|
||||||
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result = rightResultOf(
|
val result = rightResultOf(
|
||||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
||||||
|
@ -44,6 +44,8 @@ class RecordSetValidationsSpec
|
|||||||
|
|
||||||
import RecordSetValidations._
|
import RecordSetValidations._
|
||||||
|
|
||||||
|
val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone)
|
||||||
|
|
||||||
"RecordSetValidations" should {
|
"RecordSetValidations" should {
|
||||||
"validRecordTypes" should {
|
"validRecordTypes" should {
|
||||||
"return invalid request when adding a PTR record to a forward zone" in {
|
"return invalid request when adding a PTR record to a forward zone" in {
|
||||||
@ -184,24 +186,75 @@ class RecordSetValidationsSpec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"isDotted" should {
|
||||||
|
"return a failure for any record with dotted hosts if it is already present" in {
|
||||||
|
val test = aaaa.copy(name = "this.is.a.failure.")
|
||||||
|
leftValue(isDotted(test, okZone, None, false, true)) shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return a failure for any record that is a dotted host if user or record type is not allowed" in {
|
||||||
|
val test = aaaa.copy(name = "this.is.a.failure." + okZone.name)
|
||||||
|
leftValue(isDotted(test, okZone, None, true, false)) shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return success for a dotted record if it does not already have a record or zone with same name and user is allowed" in {
|
||||||
|
val test = aaaa.copy(name = "this.passes")
|
||||||
|
isDotted(test, okZone, None, true, true) should be(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return success for a new record that has the same name as the existing record" in {
|
||||||
|
val newRecord = aaaa.copy(name = "dot.ted")
|
||||||
|
val existingRecord = newRecord.copy(ttl = 330)
|
||||||
|
|
||||||
|
isDotted(newRecord, okZone, Some(existingRecord), true, true) should be(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"typeSpecificValidations" should {
|
"typeSpecificValidations" should {
|
||||||
"Run dotted hosts checks" should {
|
"Run dotted hosts checks" should {
|
||||||
val dottedARecord = rsOk.copy(name = "this.is.a.failure.")
|
val dottedARecord = rsOk.copy(name = "this.is.a.failure.")
|
||||||
"return a failure for any new record with dotted hosts in forward zones" in {
|
"return a failure for any new record with dotted hosts in forward zones" in {
|
||||||
leftValue(
|
leftValue(
|
||||||
typeSpecificValidations(dottedARecord, List(), okZone, None, Nil)
|
typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false)
|
||||||
) shouldBe an[InvalidRequest]
|
) shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
"return a failure for any new record with dotted hosts in forward zones (CNAME)" in {
|
"return a failure for any new record with dotted hosts in forward zones (CNAME)" in {
|
||||||
leftValue(
|
leftValue(
|
||||||
typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil)
|
typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false)
|
||||||
) shouldBe an[InvalidRequest]
|
) shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
"return a failure for any new record with dotted hosts in forward zones (NS)" in {
|
"return a failure for any new record with dotted hosts in forward zones (NS)" in {
|
||||||
leftValue(
|
leftValue(
|
||||||
typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil)
|
typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false)
|
||||||
|
) shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return a success for any new record with dotted hosts in forward zones if it satisfies dotted hosts configs" in {
|
||||||
|
// Zone, User, Record Type and Number of dots are all satisfied
|
||||||
|
val record = typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true, 5)
|
||||||
|
record should be(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
"return a failure for any new record with dotted hosts if no.of.dots allowed is 0" in {
|
||||||
|
// Zone, User, Record Type and Number of dots are all satisfied
|
||||||
|
leftValue(
|
||||||
|
typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true, 0)
|
||||||
|
) shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return a failure for any new record with dotted hosts in forward zones (A record) if it doesn't satisfy dotted hosts configs" in {
|
||||||
|
// 'A' record is not allowed in the config
|
||||||
|
leftValue(
|
||||||
|
typeSpecificValidations(dottedARecord.copy(zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false, 5)
|
||||||
|
) shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return a failure for any new record with dotted hosts in forward zones (NS record) if it doesn't satisfy dotted hosts configs" in {
|
||||||
|
// 'NS' record is not allowed in the config
|
||||||
|
leftValue(
|
||||||
|
typeSpecificValidations(dottedARecord.copy(typ = NS, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false, 5)
|
||||||
) shouldBe an[InvalidRequest]
|
) shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +264,10 @@ class RecordSetValidationsSpec
|
|||||||
List(),
|
List(),
|
||||||
okZone,
|
okZone,
|
||||||
Some(dottedARecord.copy(ttl = 300)),
|
Some(dottedARecord.copy(ttl = 300)),
|
||||||
Nil
|
Nil,
|
||||||
|
true,
|
||||||
|
dottedHostsConfigZonesAllowed.toSet,
|
||||||
|
false
|
||||||
) should be(right)
|
) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +278,10 @@ class RecordSetValidationsSpec
|
|||||||
List(),
|
List(),
|
||||||
okZone,
|
okZone,
|
||||||
Some(dottedCNAMERecord.copy(ttl = 300)),
|
Some(dottedCNAMERecord.copy(ttl = 300)),
|
||||||
Nil
|
Nil,
|
||||||
|
true,
|
||||||
|
dottedHostsConfigZonesAllowed.toSet,
|
||||||
|
false
|
||||||
) should be(right)
|
) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +293,10 @@ class RecordSetValidationsSpec
|
|||||||
List(),
|
List(),
|
||||||
okZone,
|
okZone,
|
||||||
Some(dottedNSRecord.copy(ttl = 300)),
|
Some(dottedNSRecord.copy(ttl = 300)),
|
||||||
Nil
|
Nil,
|
||||||
|
true,
|
||||||
|
dottedHostsConfigZonesAllowed.toSet,
|
||||||
|
false
|
||||||
)
|
)
|
||||||
) shouldBe an[InvalidRequest]
|
) shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
@ -245,35 +307,35 @@ class RecordSetValidationsSpec
|
|||||||
val test = srv.copy(name = "_sip._tcp.example.com.")
|
val test = srv.copy(name = "_sip._tcp.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success for an SRV record following convention without FQDN" in {
|
"return success for an SRV record following convention without FQDN" in {
|
||||||
val test = srv.copy(name = "_sip._tcp")
|
val test = srv.copy(name = "_sip._tcp")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success for an SRV record following convention with a record name" in {
|
"return success for an SRV record following convention with a record name" in {
|
||||||
val test = srv.copy(name = "_sip._tcp.foo.")
|
val test = srv.copy(name = "_sip._tcp.foo.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success on a wildcard SRV that follows convention" in {
|
"return success on a wildcard SRV that follows convention" in {
|
||||||
val test = srv.copy(name = "*._tcp.example.com.")
|
val test = srv.copy(name = "*._tcp.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success on a wildcard in second position SRV that follows convention" in {
|
"return success on a wildcard in second position SRV that follows convention" in {
|
||||||
val test = srv.copy(name = "_sip._*.example.com.")
|
val test = srv.copy(name = "_sip._*.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Skip dotted checks on NAPTR" should {
|
"Skip dotted checks on NAPTR" should {
|
||||||
@ -281,21 +343,21 @@ class RecordSetValidationsSpec
|
|||||||
val test = naptr.copy(name = "sub.naptr.example.com.")
|
val test = naptr.copy(name = "sub.naptr.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success for an NAPTR record without FQDN" in {
|
"return success for an NAPTR record without FQDN" in {
|
||||||
val test = naptr.copy(name = "sub.naptr")
|
val test = naptr.copy(name = "sub.naptr")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return success on a wildcard NAPTR" in {
|
"return success on a wildcard NAPTR" in {
|
||||||
val test = naptr.copy(name = "*.sub.naptr.example.com.")
|
val test = naptr.copy(name = "*.sub.naptr.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -304,7 +366,7 @@ class RecordSetValidationsSpec
|
|||||||
val test = ptrIp4.copy(name = "10.1.2.")
|
val test = ptrIp4.copy(name = "10.1.2.")
|
||||||
val zone = zoneIp4.copy(name = "198.in-addr.arpa.")
|
val zone = zoneIp4.copy(name = "198.in-addr.arpa.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Skip dotted checks on TXT" should {
|
"Skip dotted checks on TXT" should {
|
||||||
@ -312,7 +374,7 @@ class RecordSetValidationsSpec
|
|||||||
val test = txt.copy(name = "sub.txt.example.com.")
|
val test = txt.copy(name = "sub.txt.example.com.")
|
||||||
val zone = okZone.copy(name = "example.com.")
|
val zone = okZone.copy(name = "example.com.")
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zone, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -329,7 +391,7 @@ class RecordSetValidationsSpec
|
|||||||
List(SOAData(Fqdn("something"), "other", 1, 2, 3, 5, 6))
|
List(SOAData(Fqdn("something"), "other", 1, 2, 3, 5, 6))
|
||||||
)
|
)
|
||||||
|
|
||||||
typeSpecificValidations(test, List(), zoneIp4, None, Nil) should be(right)
|
typeSpecificValidations(test, List(), zoneIp4, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,29 +404,29 @@ class RecordSetValidationsSpec
|
|||||||
records = List(NSData(Fqdn("some.test.ns.")))
|
records = List(NSData(Fqdn("some.test.ns.")))
|
||||||
)
|
)
|
||||||
|
|
||||||
nsValidations(valid, okZone, None, List(new Regex(".*"))) should be(right)
|
nsValidations(valid, okZone, None, List(new Regex(".*")), true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
"return an InvalidRequest if an NS record is '@'" in {
|
"return an InvalidRequest if an NS record is '@'" in {
|
||||||
val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil))
|
val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
"return an InvalidRequest if an NS record is the same as the zone" in {
|
"return an InvalidRequest if an NS record is the same as the zone" in {
|
||||||
val invalid = invalidNsApexRs.copy(name = okZone.name)
|
val invalid = invalidNsApexRs.copy(name = okZone.name)
|
||||||
val error = leftValue(nsValidations(invalid, okZone, None, Nil))
|
val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
"return an InvalidRequest if the NS record being updated is '@'" in {
|
"return an InvalidRequest if the NS record being updated is '@'" in {
|
||||||
val valid = invalidNsApexRs.copy(name = "this-is-not-origin-mate")
|
val valid = invalidNsApexRs.copy(name = "this-is-not-origin-mate")
|
||||||
val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil))
|
val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
|
||||||
"return an InvalidRequest if an NS record data is not in the approved server list" in {
|
"return an InvalidRequest if an NS record data is not in the approved server list" in {
|
||||||
val ns = invalidNsApexRs.copy(records = List(NSData(Fqdn("not.approved."))))
|
val ns = invalidNsApexRs.copy(records = List(NSData(Fqdn("not.approved."))))
|
||||||
val error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*"))))
|
val error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,25 +434,35 @@ class RecordSetValidationsSpec
|
|||||||
"DSValidations" should {
|
"DSValidations" should {
|
||||||
val matchingNs = ns.copy(zoneId = ds.zoneId, name = ds.name, ttl = ds.ttl)
|
val matchingNs = ns.copy(zoneId = ds.zoneId, name = ds.name, ttl = ds.ttl)
|
||||||
"return ok if the record is non-origin DS with matching NS" in {
|
"return ok if the record is non-origin DS with matching NS" in {
|
||||||
dsValidations(ds, List(matchingNs), okZone) should be(right)
|
dsValidations(ds, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a DS record is '@'" in {
|
"return an InvalidRequest if a DS record is '@'" in {
|
||||||
val apex = ds.copy(name = "@")
|
val apex = ds.copy(name = "@")
|
||||||
val error = leftValue(dsValidations(apex, List(matchingNs), okZone))
|
val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a DS record is the same as the zone" in {
|
"return an InvalidRequest if a DS record is the same as the zone" in {
|
||||||
val apex = ds.copy(name = okZone.name)
|
val apex = ds.copy(name = okZone.name)
|
||||||
val error = leftValue(dsValidations(apex, List(matchingNs), okZone))
|
val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if there is no NS matching the record" in {
|
"return an InvalidRequest if there is no NS matching the record" in {
|
||||||
val error = leftValue(dsValidations(ds, List(), okZone))
|
val error = leftValue(dsValidations(ds, List(), okZone, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if the DS is dotted" in {
|
"return an InvalidRequest if the DS is dotted" in {
|
||||||
val error =
|
val error =
|
||||||
leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone))
|
leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
|
error shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
|
"return ok if the DS is dotted and zone, user, record type is allowed in dotted hosts config" in {
|
||||||
|
val record =
|
||||||
|
dsValidations(ds.copy(name = "dotted.trial", zoneId = dottedZone.id), List(matchingNs), dottedZone, true, dottedHostsConfigZonesAllowed.toSet, true, 5)
|
||||||
|
record should be(right)
|
||||||
|
}
|
||||||
|
"return an InvalidRequest if the DS is dotted and zone, user, record type is allowed in dotted hosts config but has a conflict with existing record or zone" in {
|
||||||
|
val error =
|
||||||
|
leftValue(dsValidations(ds.copy(name = "dotted.trial", zoneId = dottedZone.id), List(matchingNs), dottedZone, false, dottedHostsConfigZonesAllowed.toSet, true))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,54 +470,64 @@ class RecordSetValidationsSpec
|
|||||||
"CnameValidations" should {
|
"CnameValidations" should {
|
||||||
val invalidCnameApexRs: RecordSet = cname.copy(name = "@")
|
val invalidCnameApexRs: RecordSet = cname.copy(name = "@")
|
||||||
"return a RecordSetAlreadyExistsError if a record with the same name exists and creating a cname" in {
|
"return a RecordSetAlreadyExistsError if a record with the same name exists and creating a cname" in {
|
||||||
val error = leftValue(cnameValidations(cname, List(aaaa), okZone))
|
val error = leftValue(cnameValidations(cname, List(aaaa), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe a[RecordSetAlreadyExists]
|
error shouldBe a[RecordSetAlreadyExists]
|
||||||
}
|
}
|
||||||
"return ok if name is not '@'" in {
|
"return ok if name is not '@'" in {
|
||||||
cnameValidations(cname, List(), okZone) should be(right)
|
cnameValidations(cname, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right)
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a cname record set name is '@'" in {
|
"return an InvalidRequest if a cname record set name is '@'" in {
|
||||||
val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone))
|
val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a cname record set name is same as zone" in {
|
"return an InvalidRequest if a cname record set name is same as zone" in {
|
||||||
val invalid = invalidCnameApexRs.copy(name = okZone.name)
|
val invalid = invalidCnameApexRs.copy(name = okZone.name)
|
||||||
val error = leftValue(cnameValidations(invalid, List(), okZone))
|
val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a cname record set name is dotted" in {
|
"return an InvalidRequest if a cname record set name is dotted" in {
|
||||||
val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone))
|
val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return ok if new recordset name does not contain dot" in {
|
"return ok if new recordset name does not contain dot" in {
|
||||||
cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted"))) should be(
|
cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted")), true, dottedHostsConfigZonesAllowed.toSet, false) should be(
|
||||||
right
|
right
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"return ok if dotted host name doesn't change" in {
|
"return ok if dotted host name doesn't change" in {
|
||||||
val newRecord = cname.copy(name = "dot.ted", ttl = 500)
|
val newRecord = cname.copy(name = "dot.ted", ttl = 500)
|
||||||
cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300))) should be(
|
cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300)), true, dottedHostsConfigZonesAllowed.toSet, false) should be(
|
||||||
right
|
right
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if a cname record set name is updated to '@'" in {
|
"return an InvalidRequest if a cname record set name is updated to '@'" in {
|
||||||
val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname)))
|
val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname), true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an InvalidRequest if updated cname record set name is same as zone" in {
|
"return an InvalidRequest if updated cname record set name is same as zone" in {
|
||||||
val error =
|
val error =
|
||||||
leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname)))
|
leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname), true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
"return an RecordSetValidation error if recordset data contain more than one sequential '.'" in {
|
"return an RecordSetValidation error if recordset data contain more than one sequential '.'" in {
|
||||||
val error = leftValue(cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record..zone")))), List(), okZone))
|
val error = leftValue(cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record..zone")))), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[RecordSetValidation]
|
error shouldBe an[RecordSetValidation]
|
||||||
}
|
}
|
||||||
"return ok if recordset data does not contain sequential '.'" in {
|
"return ok if recordset data does not contain sequential '.'" in {
|
||||||
cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone) should be(
|
cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false) should be(
|
||||||
right
|
right
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
"return ok if the CNAME is dotted and zone, user, record type is allowed in dotted hosts config" in {
|
||||||
|
val record =
|
||||||
|
cnameValidations(cname.copy(name = "dot.ted", zoneId = dottedZone.id), List(), dottedZone, None, true, dottedHostsConfigZonesAllowed.toSet, true, 5)
|
||||||
|
record should be(right)
|
||||||
|
}
|
||||||
|
"return an InvalidRequest if the CNAME is dotted and zone, user, record type is allowed in dotted hosts config but has a conflict with existing record or zone" in {
|
||||||
|
val error =
|
||||||
|
leftValue(cnameValidations(cname.copy(name = "dot.ted", zoneId = dottedZone.id), List(), dottedZone, None, false, dottedHostsConfigZonesAllowed.toSet, true))
|
||||||
|
error shouldBe an[InvalidRequest]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"isNotHighValueDomain" should {
|
"isNotHighValueDomain" should {
|
||||||
|
@ -119,6 +119,8 @@ trait EmptyGroupRepo extends GroupRepository {
|
|||||||
|
|
||||||
def getGroups(groupIds: Set[String]): IO[Set[Group]] = IO.pure(Set())
|
def getGroups(groupIds: Set[String]): IO[Set[Group]] = IO.pure(Set())
|
||||||
|
|
||||||
|
def getGroupsByName(groupNames: Set[String]): IO[Set[Group]] = IO.pure(Set())
|
||||||
|
|
||||||
def getGroupByName(groupName: String): IO[Option[Group]] = IO.pure(None)
|
def getGroupByName(groupName: String): IO[Option[Group]] = IO.pure(None)
|
||||||
|
|
||||||
def getGroupsByName(groupName: String): IO[Set[Group]] = IO.pure(Set())
|
def getGroupsByName(groupName: String): IO[Set[Group]] = IO.pure(Set())
|
||||||
|
@ -31,6 +31,8 @@ trait GroupRepository extends Repository {
|
|||||||
|
|
||||||
def getGroups(groupIds: Set[String]): IO[Set[Group]]
|
def getGroups(groupIds: Set[String]): IO[Set[Group]]
|
||||||
|
|
||||||
|
def getGroupsByName(groupNames: Set[String]): IO[Set[Group]]
|
||||||
|
|
||||||
def getGroupByName(groupName: String): IO[Option[Group]]
|
def getGroupByName(groupName: String): IO[Option[Group]]
|
||||||
|
|
||||||
def getGroupsByName(groupName: String): IO[Set[Group]]
|
def getGroupsByName(groupName: String): IO[Set[Group]]
|
||||||
|
@ -36,6 +36,7 @@ object TestMembershipData {
|
|||||||
|
|
||||||
val dummyUser = User("dummyName", "dummyAccess", "dummySecret")
|
val dummyUser = User("dummyName", "dummyAccess", "dummySecret")
|
||||||
val superUser = User("super", "superAccess", "superSecret", isSuper = true)
|
val superUser = User("super", "superAccess", "superSecret", isSuper = true)
|
||||||
|
val xyzUser = User("xyz", "xyzAccess", "xyzSecret")
|
||||||
val supportUser = User("support", "supportAccess", "supportSecret", isSupport = true)
|
val supportUser = User("support", "supportAccess", "supportSecret", isSupport = true)
|
||||||
val lockedUser = User("locked", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked)
|
val lockedUser = User("locked", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked)
|
||||||
val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", "sharedSecret")
|
val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", "sharedSecret")
|
||||||
|
@ -34,6 +34,8 @@ object TestZoneData {
|
|||||||
adminGroupId = okGroup.id,
|
adminGroupId = okGroup.id,
|
||||||
connection = testConnection
|
connection = testConnection
|
||||||
)
|
)
|
||||||
|
val dottedZone: Zone = Zone("dotted.xyz.", "dotted@xyz.com", adminGroupId = xyzGroup.id)
|
||||||
|
val dotZone: Zone = Zone("dot.xyz.", "dotted@xyz.com", adminGroupId = xyzGroup.id)
|
||||||
val abcZone: Zone = Zone("abc.zone.recordsets.", "test@test.com", adminGroupId = abcGroup.id)
|
val abcZone: Zone = Zone("abc.zone.recordsets.", "test@test.com", adminGroupId = abcGroup.id)
|
||||||
val xyzZone: Zone = Zone("xyz.", "abc@xyz.com", adminGroupId = xyzGroup.id)
|
val xyzZone: Zone = Zone("xyz.", "abc@xyz.com", adminGroupId = xyzGroup.id)
|
||||||
val zoneIp4: Zone = Zone("0.162.198.in-addr.arpa.", "test@test.com", adminGroupId = abcGroup.id)
|
val zoneIp4: Zone = Zone("0.162.198.in-addr.arpa.", "test@test.com", adminGroupId = abcGroup.id)
|
||||||
|
@ -536,7 +536,66 @@ v6-discovery-nibble-boundaries {
|
|||||||
min = 5
|
min = 5
|
||||||
max = 20
|
max = 20
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dotted Hosts
|
||||||
|
|
||||||
|
Configuration setting that determines the zones, users (either individual or based on group) and record types that are
|
||||||
|
allowed to create dotted hosts. If only all the above are satisfied, one can create a dotted host in VinylDNS.
|
||||||
|
|
||||||
|
Note the following:
|
||||||
|
1. Zones defined in the `zone` must always end with a dot. Eg: `comcast.com.`
|
||||||
|
2. Wildcard character `*` can be used in `zone` to allow dotted hosts for all zones matching it.
|
||||||
|
3. Individual users who are allowed to create dotted hosts are added to the `user-list` using their username.
|
||||||
|
4. A set of users in a group who are allowed to create dotted hosts are added to the `group-list` using group name.
|
||||||
|
5. If the user is either in `user-list` or `group-list`, they are allowed to create a dotted host. It is
|
||||||
|
not necessary for the user to be in both `user-list` and `group-list`.
|
||||||
|
6. The record types which are allowed while creating a dotted host is added to the `record-types`.
|
||||||
|
7. The number of dots allowed in a record name for a zone is given in `dots-limit`.
|
||||||
|
8. If `user-list` is left empty (`user-list = []`), no user will be allowed to create dotted hosts unless
|
||||||
|
they're present in `group-list` and vice-versa. If both `user-list` and `group-list` is left empty
|
||||||
|
no users will be allowed to create dotted hosts in that zone.
|
||||||
|
9. If `record-types` is left empty (`record-types = []`), user cannot create dotted hosts of any record type
|
||||||
|
in that zone.
|
||||||
|
10. If `dots-limit` is set to 0 (`dots-limit = 0`), we cannot create dotted hosts record in that zone.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts
|
||||||
|
dotted-hosts = {
|
||||||
|
allowed-settings = [
|
||||||
|
{
|
||||||
|
zone = "dummy."
|
||||||
|
user-list = ["testuser"]
|
||||||
|
group-list = ["dummy-group"]
|
||||||
|
record-types = ["AAAA"]
|
||||||
|
dots-limit = 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# for wildcard zones. Settings will be applied to all matching zones
|
||||||
|
zone = "*ent.com."
|
||||||
|
user-list = ["professor", "testuser"]
|
||||||
|
group-list = ["testing-group"]
|
||||||
|
record-types = ["A", "CNAME"]
|
||||||
|
dots-limit = 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above, the dotted hosts can be created only in the zone `dummy.` and zones matching `*ent.com.` (parent.com., child.parent.com.)
|
||||||
|
|
||||||
|
Also, it must satisfy the allowed users or group users and record type of the respective zone to create a dotted host.
|
||||||
|
|
||||||
|
For eg, we can't create a dotted host with `CNAME` record type in the zone `dummy.` as it's not in `record-types`.
|
||||||
|
And the user `professor` can't create a dotted host in the zone `dummy.` as the user is not in `user-list` or
|
||||||
|
`group-list` (not part of `dummy-group`).
|
||||||
|
|
||||||
|
The config can be left empty as follows if we don't want to use it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dotted-hosts = {
|
||||||
|
allowed-settings = []
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Example Config
|
### Full Example Config
|
||||||
@ -713,6 +772,27 @@ v6-discovery-nibble-boundaries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts
|
||||||
|
dotted-hosts = {
|
||||||
|
allowed-settings = [
|
||||||
|
{
|
||||||
|
zone = "dummy."
|
||||||
|
user-list = ["testuser"]
|
||||||
|
group-list = ["dummy-group"]
|
||||||
|
record-types = ["AAAA"]
|
||||||
|
dots-limit = 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# for wildcard zones. Settings will be applied to all matching zones
|
||||||
|
zone = "*ent.com."
|
||||||
|
user-list = ["professor", "testuser"]
|
||||||
|
group-list = ["testing-group"]
|
||||||
|
record-types = ["A", "CNAME"]
|
||||||
|
dots-limit = 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# true if you want to enable manual review for non-fatal errors
|
# true if you want to enable manual review for non-fatal errors
|
||||||
manual-batch-review-enabled = true
|
manual-batch-review-enabled = true
|
||||||
|
|
||||||
|
@ -93,6 +93,24 @@ class MySqlGroupRepositoryIntegrationSpec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"MySqlGroupRepository.getGroupsByName" should {
|
||||||
|
"omits all non existing groups" in {
|
||||||
|
val result = repo.getGroupsByName(Set("no-existo", groups.head.name)).unsafeRunSync()
|
||||||
|
result should contain theSameElementsAs Set(groups.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
"returns correct list of groups" in {
|
||||||
|
val names = Set(groups(0).name, groups(1).name, groups(2).name)
|
||||||
|
val result = repo.getGroupsByName(names).unsafeRunSync()
|
||||||
|
result should contain theSameElementsAs groups.take(3).toSet
|
||||||
|
}
|
||||||
|
|
||||||
|
"returns empty list when given no names" in {
|
||||||
|
val result = repo.getGroupsByName(Set[String]()).unsafeRunSync()
|
||||||
|
result should contain theSameElementsAs Set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"MySqlGroupRepository.getGroupByName" should {
|
"MySqlGroupRepository.getGroupByName" should {
|
||||||
"retrieve a group" in {
|
"retrieve a group" in {
|
||||||
repo.getGroupByName(groups.head.name).unsafeRunSync() shouldBe Some(groups.head)
|
repo.getGroupByName(groups.head.name).unsafeRunSync() shouldBe Some(groups.head)
|
||||||
|
@ -69,6 +69,13 @@ class MySqlGroupRepository extends GroupRepository with GroupProtobufConversions
|
|||||||
| WHERE id
|
| WHERE id
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
|
private val BASE_GET_GROUPS_BY_NAMES =
|
||||||
|
"""
|
||||||
|
|SELECT data
|
||||||
|
| FROM groups
|
||||||
|
| WHERE name
|
||||||
|
""".stripMargin
|
||||||
|
|
||||||
def save(db: DB, group: Group): IO[Group] =
|
def save(db: DB, group: Group): IO[Group] =
|
||||||
monitor("repo.Group.save") {
|
monitor("repo.Group.save") {
|
||||||
IO {
|
IO {
|
||||||
@ -141,6 +148,27 @@ class MySqlGroupRepository extends GroupRepository with GroupProtobufConversions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getGroupsByName(groupNames: Set[String]): IO[Set[Group]] =
|
||||||
|
monitor("repo.Group.getGroups") {
|
||||||
|
IO {
|
||||||
|
logger.debug(s"Getting group with names: $groupNames")
|
||||||
|
if (groupNames.isEmpty)
|
||||||
|
Set[Group]()
|
||||||
|
else {
|
||||||
|
DB.readOnly { implicit s =>
|
||||||
|
val groupNameList = groupNames.toList
|
||||||
|
val inClause = " IN (" + groupNameList.as("?").mkString(",") + ")"
|
||||||
|
val query = BASE_GET_GROUPS_BY_NAMES + inClause
|
||||||
|
SQL(query)
|
||||||
|
.bind(groupNameList: _*)
|
||||||
|
.map(toGroup(1))
|
||||||
|
.list()
|
||||||
|
.apply()
|
||||||
|
}.toSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getGroupByName(groupName: String): IO[Option[Group]] =
|
def getGroupByName(groupName: String): IO[Option[Group]] =
|
||||||
monitor("repo.Group.getGroupByName") {
|
monitor("repo.Group.getGroupByName") {
|
||||||
IO {
|
IO {
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
<tr ng-repeat="(recordName, record) in records track by $index">
|
<tr ng-repeat="(recordName, record) in records track by $index">
|
||||||
<td>
|
<td>
|
||||||
<div ng-if="record.isDotted && record.type != 'TXT' && record.type != 'SRV' && record.type != 'NAPTR'" class="text-danger wrap-long-text" data-toggle="tooltip" data-placement="top"
|
<div ng-if="record.isDotted && record.type != 'TXT' && record.type != 'SRV' && record.type != 'NAPTR'" class="text-danger wrap-long-text" data-toggle="tooltip" data-placement="top"
|
||||||
title="Dotted hosts are invalid! Please delete or update without a '.'">
|
title="This is a dotted host!">
|
||||||
{{record.name}} <span class="fa fa-warning" />
|
{{record.name}} <span class="fa fa-warning" />
|
||||||
</div>
|
</div>
|
||||||
<div class="wrap-long-text" ng-if="!record.isDotted || (record.type != 'TXT' || record.type != 'SRV' || record.type != 'NAPTR')">
|
<div class="wrap-long-text" ng-if="!record.isDotted || (record.type != 'TXT' || record.type != 'SRV' || record.type != 'NAPTR')">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user