From c6def527bcb14605ea877502098e2a89979c39f6 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 26 Aug 2022 16:28:32 +0530 Subject: [PATCH 01/30] Allow dotted host with config --- .../api/src/main/resources/application.conf | 5 ++ modules/api/src/main/resources/reference.conf | 5 ++ .../src/main/scala/vinyldns/api/Boot.scala | 4 +- .../api/config/DottedHostsConfig.scala | 28 +++++++ .../vinyldns/api/config/VinylDNSConfig.scala | 3 + .../api/domain/batch/BatchChangeService.scala | 46 +++++++---- .../domain/batch/BatchChangeValidations.scala | 53 ++++++++----- .../api/domain/record/RecordSetService.scala | 39 +++++++++- .../domain/record/RecordSetValidations.scala | 78 +++++++++++++++---- .../core/domain/DomainValidationErrors.scala | 6 ++ .../core/domain/SingleChangeError.scala | 3 +- 11 files changed, 215 insertions(+), 55 deletions(-) create mode 100644 modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index a8aa7175c..be699fb20 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,6 +165,11 @@ vinyldns { "ns1.parent.com4." ] + # approved zones/domains that are allowed to create dotted hosts + zone-list = [ + "ok." # for local testing + ] + # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production crypto { type = "vinyldns.core.crypto.NoOpCrypto" diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index b82cb74d6..c60afc5f0 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -90,6 +90,11 @@ vinyldns { "ns1.parent.com." ] + # approved zones/domains that are allowed to create dotted hosts + zone-list = [ + "ok." # for local testing + ] + # color should be green or blue, used in order to do blue/green deployment color = "green" diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 24db9b737..782ee3d61 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -121,7 +121,8 @@ object Boot extends App { vinyldnsConfig.highValueDomainConfig, vinyldnsConfig.manualReviewConfig, vinyldnsConfig.batchChangeConfig, - vinyldnsConfig.scheduledChangesConfig + vinyldnsConfig.scheduledChangesConfig, + vinyldnsConfig.dottedHostsConfig ) val membershipService = MembershipService(repositories) @@ -139,6 +140,7 @@ object Boot extends App { backendResolver, vinyldnsConfig.serverConfig.validateRecordLookupAgainstDnsBackend, vinyldnsConfig.highValueDomainConfig, + vinyldnsConfig.dottedHostsConfig, vinyldnsConfig.serverConfig.approvedNameServers, vinyldnsConfig.serverConfig.useRecordSetCache ) diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala new file mode 100644 index 000000000..e6a375d69 --- /dev/null +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -0,0 +1,28 @@ +/* + * 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 + +final case class DottedHostsConfig(zoneList: List[String]) +object DottedHostsConfig { + implicit val configReader: ConfigReader[DottedHostsConfig] = + ConfigReader.forProduct1[DottedHostsConfig, List[String]]( + "zone-list" + )(zoneList => + DottedHostsConfig(zoneList)) +} diff --git a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala index abb75c4be..fc5a0d42e 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala @@ -47,6 +47,7 @@ final case class VinylDNSConfig( notifierConfigs: List[NotifierConfig], dataStoreConfigs: List[DataStoreConfig], backendConfigs: BackendConfigs, + dottedHostsConfig: DottedHostsConfig, configuredDnsConnections: ConfiguredDnsConnections, apiMetricSettings: APIMetricsSettings, crypto: CryptoAlgebra, @@ -85,6 +86,7 @@ object VinylDNSConfig { serverConfig <- loadIO[ServerConfig](config, "vinyldns") batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns") backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend") + dottedHostsConfig <- loadIO[DottedHostsConfig](config, "vinyldns") httpConfig <- loadIO[HttpConfig](config, "vinyldns.rest") hvdConfig <- loadIO[HighValueDomainConfig](config, "vinyldns.high-value-domains") scheduledChangesConfig <- loadIO[ScheduledChangesConfig](config, "vinyldns") @@ -110,6 +112,7 @@ object VinylDNSConfig { notifierConfigs, dataStoreConfigs, backendConfigs, + dottedHostsConfig, connections, metricSettings, crypto, diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index 2bccc1699..1a28df53d 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -32,21 +32,10 @@ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.batch.BatchChangeApprovalStatus.BatchChangeApprovalStatus import vinyldns.core.domain.batch._ import vinyldns.core.domain.batch.BatchChangeApprovalStatus._ -import vinyldns.core.domain.{ - CnameAtZoneApexError, - SingleChangeError, - UserIsNotAuthorizedError, - ZoneDiscoveryError -} -import vinyldns.core.domain.membership.{ - Group, - GroupRepository, - ListUsersResults, - User, - UserRepository -} +import vinyldns.core.domain.{CnameAtZoneApexError, SingleChangeError, UserIsNotAuthorizedError, ZoneDiscoveryError} +import vinyldns.core.domain.membership.{Group, GroupRepository, ListUsersResults, User, UserRepository} import vinyldns.core.domain.record.RecordType._ -import vinyldns.core.domain.record.RecordSetRepository +import vinyldns.core.domain.record.{RecordSet, RecordSetRepository} import vinyldns.core.domain.zone.ZoneRepository import vinyldns.core.notifier.{AllNotifiers, Notification} @@ -133,16 +122,43 @@ class BatchChangeService( recordSets <- getExistingRecordSets(changesWithZones, zoneMap).toBatchResult withTtl = doTtlMapping(changesWithZones, recordSets) groupedChanges = ChangeForValidationMap(withTtl, recordSets) + zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(groupedChanges).toBatchResult validatedSingleChanges = validateChangesWithContext( groupedChanges, auth, isApproved, - batchChangeInput.ownerGroupId + batchChangeInput.ownerGroupId, + zoneOrRecordDoesNotAlreadyExist ) errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges) validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges) } yield BatchValidationFlowOutput(validatedSingleChangesWithGroups, zoneMap, groupedChanges) + // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not + def zoneOrRecordDoesNotExist(groupedChanges: ChangeForValidationMap): IO[Boolean] = { + val inputChange = groupedChanges.changes.map(x => x.toOption).map(x => x.get) + // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist + val newRecordFqdn = inputChange.map(_.recordName).head + "." + inputChange.map(_.zone.name).head + for { + zone <- zoneRepository.getZoneByName(newRecordFqdn) + record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) + isZoneAlreadyExist = zone.isDefined + isRecordAlreadyExist = doesRecordWithSameTypeExist(record, inputChange.map(_.inputChange)) + doesNotExist = if(isZoneAlreadyExist || 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: List[ChangeInput]): Boolean = { + if(oldRecord.nonEmpty) { + val typeExists = oldRecord.map(x => x.typ == newRecord.map(_.typ)) + if (typeExists.contains(true)) true else false + } + else { + false + } + } + def getGroupIdsFromUnauthorizedErrors( changes: ValidatedBatch[ChangeForValidation] ): BatchResult[Set[Group]] = { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index efa72e443..f3354fbc3 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -17,15 +17,9 @@ package vinyldns.api.domain.batch import java.net.InetAddress - import cats.data._ import cats.implicits._ -import vinyldns.api.config.{ - BatchChangeConfig, - HighValueDomainConfig, - ManualReviewConfig, - ScheduledChangesConfig -} +import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.DomainValidations._ import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.auth.AuthPrincipal @@ -54,7 +48,8 @@ trait BatchChangeValidationsAlgebra { groupedChanges: ChangeForValidationMap, auth: AuthPrincipal, isApproved: Boolean, - batchOwnerGroupId: Option[String] + batchOwnerGroupId: Option[String], + zoneOrRecordDoesNotAlreadyExist: Boolean ): ValidatedBatch[ChangeForValidation] def canGetBatchChange( @@ -85,7 +80,8 @@ class BatchChangeValidations( highValueDomainConfig: HighValueDomainConfig, manualReviewConfig: ManualReviewConfig, batchChangeConfig: BatchChangeConfig, - scheduledChangesConfig: ScheduledChangesConfig + scheduledChangesConfig: ScheduledChangesConfig, + dottedHostsConfig: DottedHostsConfig ) extends BatchChangeValidationsAlgebra { import RecordType._ @@ -275,7 +271,8 @@ class BatchChangeValidations( groupedChanges: ChangeForValidationMap, auth: AuthPrincipal, isApproved: Boolean, - batchOwnerGroupId: Option[String] + batchOwnerGroupId: Option[String], + zoneOrRecordDoesNotAlreadyExist: Boolean ): ValidatedBatch[ChangeForValidation] = // Updates are a combination of an add and delete for a record with the same name and type in a zone. groupedChanges.changes.mapValid { @@ -283,7 +280,7 @@ class BatchChangeValidations( if groupedChanges .getLogicalChangeType(add.recordKey) .contains(LogicalChangeType.Add) => - validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId) + validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist) case addUpdate: AddChangeForValidation => validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId) // These cases MUST be below adds because: @@ -298,11 +295,28 @@ class BatchChangeValidations( validateDeleteUpdateWithContext(deleteUpdate, groupedChanges, auth, isApproved) } - def newRecordSetIsNotDotted(change: AddChangeForValidation): SingleValidation[Unit] = - if (change.recordName != change.zone.name && change.recordName.contains(".")) - ZoneDiscoveryError(change.inputChange.inputName).invalidNel - else - ().validNel + // Check if the new record set has dots and if so whether they are allowed or not + def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean): SingleValidation[Unit] = { + + // Check if the zone of the record set is present in dotted hosts config list + val isDomainAllowed = dottedHostsConfig.zoneList.contains(change.zone.name) + + // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config + if(change.recordName.contains(".") && isDomainAllowed) { + // If there is a zone or record already present which conflicts with the new dotted record, throw an error + if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name) + DottedHostError(change.recordName, change.inputChange.typ.toString).invalidNel + else + ().validNel + } + else { + // If the recordset contains dot but is not in the allowed zones to create dotted records, throw an error + if (change.recordName != change.zone.name && change.recordName.contains(".")) + ZoneDiscoveryError(change.inputChange.inputName).invalidNel + else + ().validNel + } + } def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean = existingRecordSetData.exists { rd => @@ -412,14 +426,15 @@ class BatchChangeValidations( groupedChanges: ChangeForValidationMap, auth: AuthPrincipal, isApproved: Boolean, - ownerGroupId: Option[String] + ownerGroupId: Option[String], + zoneOrRecordDoesNotAlreadyExist: Boolean ): SingleValidation[ChangeForValidation] = { val typedValidations = change.inputChange.typ match { case A | AAAA | MX => - newRecordSetIsNotDotted(change) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| - newRecordSetIsNotDotted(change) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist) case TXT | PTR => ().validNel case other => diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index cc4f18331..517fbcc47 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -28,7 +28,7 @@ import vinyldns.core.queue.MessageQueue import cats.data._ import cats.effect.IO import org.xbill.DNS.ReverseMap -import vinyldns.api.config.HighValueDomainConfig +import vinyldns.api.config.{DottedHostsConfig, HighValueDomainConfig} import vinyldns.api.domain.DomainValidations.{validateIpv4Address, validateIpv6Address} import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.record.NameSort.NameSort @@ -46,6 +46,7 @@ object RecordSetService { backendResolver: BackendResolver, validateRecordLookupAgainstDnsBackend: Boolean, highValueDomainConfig: HighValueDomainConfig, + dottedHostsConfig: DottedHostsConfig, approvedNameServers: List[Regex], useRecordSetCache: Boolean ): RecordSetService = @@ -61,6 +62,7 @@ object RecordSetService { backendResolver, validateRecordLookupAgainstDnsBackend, highValueDomainConfig, + dottedHostsConfig, approvedNameServers, useRecordSetCache ) @@ -78,6 +80,7 @@ class RecordSetService( backendResolver: BackendResolver, validateRecordLookupAgainstDnsBackend: Boolean, highValueDomainConfig: HighValueDomainConfig, + dottedHostsConfig: DottedHostsConfig, approvedNameServers: List[Regex], useRecordSetCache: Boolean ) extends RecordSetServiceAlgebra { @@ -107,12 +110,15 @@ class RecordSetService( ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult + zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, zone, None, - approvedNameServers + approvedNameServers, + zoneOrRecordDoesNotAlreadyExist, + dottedHostsConfig ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -143,12 +149,15 @@ class RecordSetService( validateRecordLookupAgainstDnsBackend ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult + zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, zone, Some(existing), - approvedNameServers + approvedNameServers, + zoneOrRecordDoesNotAlreadyExist, + dottedHostsConfig ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -169,6 +178,30 @@ class RecordSetService( _ <- messageQueue.send(change).toResult[Unit] } yield change + // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not + def zoneOrRecordDoesNotExist(newRecordSet: RecordSet, zone: Zone): IO[Boolean] = { + // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist + val newRecordFqdn = newRecordSet.name + "." + zone.name + for { + zone <- zoneRepository.getZoneByName(newRecordFqdn) + record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) + isZoneAlreadyExist = zone.isDefined + isRecordAlreadyExist = doesRecordWithSameTypeExist(record, newRecordSet) + doesNotExist = if(isZoneAlreadyExist || 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 + } + } + def getRecordSet( recordSetId: String, authPrincipal: AuthPrincipal diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 0bff5984a..b82490667 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -19,14 +19,14 @@ package vinyldns.api.domain.record import cats.syntax.either._ import vinyldns.api.Interfaces._ import vinyldns.api.backend.dns.DnsConversions -import vinyldns.api.config.HighValueDomainConfig +import vinyldns.api.config.{DottedHostsConfig, HighValueDomainConfig} import vinyldns.api.domain._ import vinyldns.core.domain.DomainHelpers._ import vinyldns.core.domain.record.RecordType._ import vinyldns.api.domain.zone._ import vinyldns.core.domain.auth.AuthPrincipal 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.Messages._ @@ -90,6 +90,44 @@ object RecordSetValidations { !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, + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: DottedHostsConfig + ): Either[Throwable, Unit] = { + + // Check if the zone of the record set is present in dotted hosts config list + val isDomainAllowed = dottedHostConfig.zoneList.contains(zone.name) + + // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config + if(newRecordSet.name.contains(".") && isDomainAllowed) { + isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist) + } + else{ + isNotDotted(newRecordSet, zone, existingRecordSet) + } + } + + // Check if there is a zone or record already present which conflicts with the new dotted record. If so, throw an error + def isDotted( + newRecordSet: RecordSet, + zone: Zone, + existingRecordSet: Option[RecordSet] = None, + zoneOrRecordDoesNotAlreadyExist: Boolean + ): Either[Throwable, Unit] = + ensuring( + InvalidRequest( + s"Record with name ${newRecordSet.name} and type ${newRecordSet.typ} already exists. " + + s"Please check the record and zone that's already present and make the change there." + ) + )( + (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist + ) + + // Check if the recordset contains dot but is not in the allowed zones to create dotted records. If so, throw an error def isNotDotted( newRecordSet: RecordSet, zone: Zone, @@ -110,16 +148,18 @@ object RecordSetValidations { existingRecordsWithName: List[RecordSet], zone: Zone, existingRecordSet: Option[RecordSet], - approvedNameServers: List[Regex] + approvedNameServers: List[Regex], + zoneOrRecordDoesNotAlreadyExist: Boolean = true, + dottedHostConfig: DottedHostsConfig ): Either[Throwable, Unit] = newRecordSet.typ match { - case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet) - case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers) - case SOA => soaValidations(newRecordSet, zone) + case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) case PTR => ptrValidations(newRecordSet, zone) case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check - case DS => dsValidations(newRecordSet, existingRecordsWithName, zone) - case _ => isNotDotted(newRecordSet, zone, existingRecordSet) + case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) } def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = @@ -140,7 +180,9 @@ object RecordSetValidations { newRecordSet: RecordSet, existingRecordsWithName: List[RecordSet], zone: Zone, - existingRecordSet: Option[RecordSet] = None + existingRecordSet: Option[RecordSet] = None, + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: DottedHostsConfig ): Either[Throwable, Unit] = { // cannot create a cname record if a record with the same exists val noRecordWithName = { @@ -173,7 +215,7 @@ object RecordSetValidations { ) _ <- noRecordWithName _ <- RDataWithConsecutiveDots - _ <- isNotDotted(newRecordSet, zone, existingRecordSet) + _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) } yield () } @@ -181,7 +223,9 @@ object RecordSetValidations { def dsValidations( newRecordSet: RecordSet, existingRecordsWithName: List[RecordSet], - zone: Zone + zone: Zone, + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: DottedHostsConfig ): Either[Throwable, Unit] = { // see https://tools.ietf.org/html/rfc4035#section-2.4 val nsChecks = existingRecordsWithName.find(_.typ == NS) match { @@ -194,7 +238,7 @@ object RecordSetValidations { } for { - _ <- isNotDotted(newRecordSet, zone) + _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) _ <- isNotOrigin( newRecordSet, zone, @@ -208,10 +252,12 @@ object RecordSetValidations { newRecordSet: RecordSet, zone: Zone, oldRecordSet: Option[RecordSet], - approvedNameServers: List[Regex] + approvedNameServers: List[Regex], + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: DottedHostsConfig ): Either[Throwable, Unit] = { // 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, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight for { _ <- isNotDottedHost @@ -233,9 +279,9 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = + def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: DottedHostsConfig): Either[Throwable, Unit] = // 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, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = // TODO we don't check for PTR as dotted...not sure why diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 91486ce79..6b56663d1 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -109,6 +109,12 @@ final case class ZoneDiscoveryError(name: String, fatal: Boolean = false) "If zone exists, then it must be connected to in VinylDNS." } +final case class DottedHostError(name: String, rsType: String) extends DomainValidationError { + def message: String = + s"Record with name $name and type $rsType already exists. " + + s"Please check the record and zone that's already present and make the change there." +} + final case class RecordAlreadyExists(name: String) extends DomainValidationError { def message: String = s"""Record "$name" Already Exists: cannot add an existing record; to update it, """ + diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index a7ed47b4e..c2d8aba44 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -32,7 +32,7 @@ object DomainValidationErrorType extends Enumeration { val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMxPreference, - InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, + InvalidBatchRecordType, ZoneDiscoveryError, DottedHostError, RecordAlreadyExists, RecordDoesNotExist, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, @@ -57,6 +57,7 @@ object DomainValidationErrorType extends Enumeration { case _: InvalidMxPreference => InvalidMxPreference case _: InvalidBatchRecordType => InvalidBatchRecordType case _: ZoneDiscoveryError => ZoneDiscoveryError + case _: DottedHostError => DottedHostError case _: RecordAlreadyExists => RecordAlreadyExists case _: RecordDoesNotExist => RecordDoesNotExist case _: CnameIsNotUniqueError => CnameIsNotUniqueError From 96f179d6948db13cfa057b3fd3460f0703e198c7 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 5 Sep 2022 13:53:33 +0530 Subject: [PATCH 02/30] Allow wildcard zones in config --- .../api/src/main/resources/application.conf | 3 +- .../src/main/scala/vinyldns/api/Boot.scala | 6 ++-- .../api/domain/batch/BatchChangeService.scala | 33 ++++++++++++++++--- .../domain/batch/BatchChangeValidations.scala | 24 +++++++------- .../api/domain/record/RecordSetService.scala | 25 ++++++++++++-- .../domain/record/RecordSetValidations.scala | 16 ++++----- 6 files changed, 78 insertions(+), 29 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index be699fb20..9091cdc7c 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -167,7 +167,8 @@ vinyldns { # approved zones/domains that are allowed to create dotted hosts zone-list = [ - "ok." # for local testing + "dummy.", # for local testing + "*en.", # for local testing with wildcard zones (allows 'open.' zone) ] # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 782ee3d61..aa7cffb33 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -121,8 +121,7 @@ object Boot extends App { vinyldnsConfig.highValueDomainConfig, vinyldnsConfig.manualReviewConfig, vinyldnsConfig.batchChangeConfig, - vinyldnsConfig.scheduledChangesConfig, - vinyldnsConfig.dottedHostsConfig + vinyldnsConfig.scheduledChangesConfig ) val membershipService = MembershipService(repositories) @@ -185,7 +184,8 @@ object Boot extends App { notifiers, vinyldnsConfig.scheduledChangesConfig.enabled, vinyldnsConfig.batchChangeConfig.v6DiscoveryNibbleBoundaries, - vinyldnsConfig.serverConfig.defaultTtl + vinyldnsConfig.serverConfig.defaultTtl, + vinyldnsConfig.dottedHostsConfig ) val collectorRegistry = CollectorRegistry.defaultRegistry val vinyldnsService = new VinylDNSService( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index 1a28df53d..d35bcaf16 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -27,6 +27,7 @@ import vinyldns.api.domain.auth.AuthPrincipalProvider import vinyldns.api.domain.batch.BatchChangeInterfaces._ import vinyldns.api.domain.batch.BatchTransformations._ import vinyldns.api.backend.dns.DnsConversions._ +import vinyldns.api.config.DottedHostsConfig import vinyldns.api.repository.ApiDataAccessor import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.batch.BatchChangeApprovalStatus.BatchChangeApprovalStatus @@ -49,7 +50,8 @@ object BatchChangeService { notifiers: AllNotifiers, scheduledChangesEnabled: Boolean, v6DiscoveryNibbleBoundaries: V6DiscoveryNibbleBoundaries, - defaultTtl: Long + defaultTtl: Long, + dottedHostsConfig: DottedHostsConfig ): BatchChangeService = new BatchChangeService( dataAccessor.zoneRepository, @@ -64,7 +66,8 @@ object BatchChangeService { notifiers, scheduledChangesEnabled, v6DiscoveryNibbleBoundaries, - defaultTtl + defaultTtl, + dottedHostsConfig ) } @@ -81,7 +84,8 @@ class BatchChangeService( notifiers: AllNotifiers, scheduledChangesEnabled: Boolean, v6zoneNibbleBoundaries: V6DiscoveryNibbleBoundaries, - defaultTtl: Long + defaultTtl: Long, + dottedHostsConfig: DottedHostsConfig ) extends BatchChangeServiceAlgebra { import batchChangeValidations._ @@ -122,13 +126,15 @@ class BatchChangeService( recordSets <- getExistingRecordSets(changesWithZones, zoneMap).toBatchResult withTtl = doTtlMapping(changesWithZones, recordSets) groupedChanges = ChangeForValidationMap(withTtl, recordSets) + allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toBatchResult zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(groupedChanges).toBatchResult validatedSingleChanges = validateChangesWithContext( groupedChanges, auth, isApproved, batchChangeInput.ownerGroupId, - zoneOrRecordDoesNotAlreadyExist + zoneOrRecordDoesNotAlreadyExist, + allowedZoneList ) errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges) validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges) @@ -159,6 +165,25 @@ class BatchChangeService( } } + // 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) + } + } + def getGroupIdsFromUnauthorizedErrors( changes: ValidatedBatch[ChangeForValidation] ): BatchResult[Set[Group]] = { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index f3354fbc3..76a0eb6fd 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -19,7 +19,7 @@ package vinyldns.api.domain.batch import java.net.InetAddress import cats.data._ import cats.implicits._ -import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{BatchChangeConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.DomainValidations._ import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.auth.AuthPrincipal @@ -49,7 +49,8 @@ trait BatchChangeValidationsAlgebra { auth: AuthPrincipal, isApproved: Boolean, batchOwnerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: Set[String] ): ValidatedBatch[ChangeForValidation] def canGetBatchChange( @@ -80,8 +81,7 @@ class BatchChangeValidations( highValueDomainConfig: HighValueDomainConfig, manualReviewConfig: ManualReviewConfig, batchChangeConfig: BatchChangeConfig, - scheduledChangesConfig: ScheduledChangesConfig, - dottedHostsConfig: DottedHostsConfig + scheduledChangesConfig: ScheduledChangesConfig ) extends BatchChangeValidationsAlgebra { import RecordType._ @@ -272,7 +272,8 @@ class BatchChangeValidations( auth: AuthPrincipal, isApproved: Boolean, batchOwnerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: Set[String] ): ValidatedBatch[ChangeForValidation] = // Updates are a combination of an add and delete for a record with the same name and type in a zone. groupedChanges.changes.mapValid { @@ -280,7 +281,7 @@ class BatchChangeValidations( if groupedChanges .getLogicalChangeType(add.recordKey) .contains(LogicalChangeType.Add) => - validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist) + validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) case addUpdate: AddChangeForValidation => validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId) // These cases MUST be below adds because: @@ -296,10 +297,10 @@ class BatchChangeValidations( } // Check if the new record set has dots and if so whether they are allowed or not - def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean): SingleValidation[Unit] = { + def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: Set[String]): SingleValidation[Unit] = { // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostsConfig.zoneList.contains(change.zone.name) + val isDomainAllowed = dottedHostConfig.contains(change.zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config if(change.recordName.contains(".") && isDomainAllowed) { @@ -427,14 +428,15 @@ class BatchChangeValidations( auth: AuthPrincipal, isApproved: Boolean, ownerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean + zoneOrRecordDoesNotAlreadyExist: Boolean, + dottedHostConfig: Set[String] ): SingleValidation[ChangeForValidation] = { val typedValidations = change.inputChange.typ match { case A | AAAA | MX => - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig: Set[String]) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig: Set[String]) case TXT | PTR => ().validNel case other => diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 517fbcc47..c7f97aac6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -110,6 +110,7 @@ class RecordSetService( ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult + allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -118,7 +119,7 @@ class RecordSetService( None, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, - dottedHostsConfig + allowedZoneList ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -149,6 +150,7 @@ class RecordSetService( validateRecordLookupAgainstDnsBackend ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult + allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -157,7 +159,7 @@ class RecordSetService( Some(existing), approvedNameServers, zoneOrRecordDoesNotAlreadyExist, - dottedHostsConfig + allowedZoneList ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -202,6 +204,25 @@ class RecordSetService( } } + // 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) + } + } + def getRecordSet( recordSetId: String, authPrincipal: AuthPrincipal diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index b82490667..6124acefe 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -19,7 +19,7 @@ package vinyldns.api.domain.record import cats.syntax.either._ import vinyldns.api.Interfaces._ import vinyldns.api.backend.dns.DnsConversions -import vinyldns.api.config.{DottedHostsConfig, HighValueDomainConfig} +import vinyldns.api.config.HighValueDomainConfig import vinyldns.api.domain._ import vinyldns.core.domain.DomainHelpers._ import vinyldns.core.domain.record.RecordType._ @@ -96,11 +96,11 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: DottedHostsConfig + dottedHostConfig: Set[String] ): Either[Throwable, Unit] = { // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostConfig.zoneList.contains(zone.name) + val isDomainAllowed = dottedHostConfig.contains(zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config if(newRecordSet.name.contains(".") && isDomainAllowed) { @@ -150,7 +150,7 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet], approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean = true, - dottedHostConfig: DottedHostsConfig + dottedHostConfig: Set[String] ): Either[Throwable, Unit] = newRecordSet.typ match { case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) @@ -182,7 +182,7 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: DottedHostsConfig + dottedHostConfig: Set[String] ): Either[Throwable, Unit] = { // cannot create a cname record if a record with the same exists val noRecordWithName = { @@ -225,7 +225,7 @@ object RecordSetValidations { existingRecordsWithName: List[RecordSet], zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: DottedHostsConfig + dottedHostConfig: Set[String] ): Either[Throwable, Unit] = { // see https://tools.ietf.org/html/rfc4035#section-2.4 val nsChecks = existingRecordsWithName.find(_.typ == NS) match { @@ -254,7 +254,7 @@ object RecordSetValidations { oldRecordSet: Option[RecordSet], approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: DottedHostsConfig + dottedHostConfig: Set[String] ): Either[Throwable, Unit] = { // TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight @@ -279,7 +279,7 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: DottedHostsConfig): Either[Throwable, Unit] = + def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: Set[String]): Either[Throwable, Unit] = // TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight From cb90f790bd353eb589b5776725caccfa2fdc70a3 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 6 Sep 2022 17:34:56 +0530 Subject: [PATCH 03/30] Update functionality --- .../RecordSetServiceIntegrationSpec.scala | 1 + .../api/src/main/resources/application.conf | 2 +- .../domain/batch/BatchChangeValidations.scala | 4 +- .../api/domain/record/RecordSetService.scala | 13 +- .../domain/record/RecordSetValidations.scala | 6 +- .../vinyldns/api/VinylDNSTestHelpers.scala | 4 +- .../domain/batch/BatchChangeServiceSpec.scala | 24 +- .../batch/BatchChangeValidationsSpec.scala | 212 +++++++++++++----- .../domain/record/RecordSetServiceSpec.scala | 14 ++ .../record/RecordSetValidationsSpec.scala | 82 +++---- .../core/domain/DomainValidationErrors.scala | 6 +- .../scala/vinyldns/core/TestZoneData.scala | 1 + 12 files changed, 258 insertions(+), 111 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index df845b477..1cf0d50f6 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -300,6 +300,7 @@ class RecordSetServiceIntegrationSpec mockBackendResolver, false, vinyldnsConfig.highValueDomainConfig, + vinyldnsConfig.dottedHostsConfig, vinyldnsConfig.serverConfig.approvedNameServers, useRecordSetCache = true ) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 9091cdc7c..8b7df4d5c 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -168,7 +168,7 @@ vinyldns { # approved zones/domains that are allowed to create dotted hosts zone-list = [ "dummy.", # for local testing - "*en.", # for local testing with wildcard zones (allows 'open.' zone) + "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) ] # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 76a0eb6fd..1ac65e91c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -303,10 +303,10 @@ class BatchChangeValidations( val isDomainAllowed = dottedHostConfig.contains(change.zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config - if(change.recordName.contains(".") && isDomainAllowed) { + if((change.recordName.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && change.recordName != change.zone.name) { // If there is a zone or record already present which conflicts with the new dotted record, throw an error if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name) - DottedHostError(change.recordName, change.inputChange.typ.toString).invalidNel + DottedHostError(change.recordName, change.zone.name).invalidNel else ().validNel } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index c7f97aac6..719f2595c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -183,13 +183,22 @@ class RecordSetService( // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not def zoneOrRecordDoesNotExist(newRecordSet: RecordSet, zone: Zone): IO[Boolean] = { // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist - val newRecordFqdn = newRecordSet.name + "." + zone.name + val newRecordFqdn = if(newRecordSet.name != zone.name) newRecordSet.name + "." + zone.name else newRecordSet.name + + val possibleZones = if(newRecordSet.name.contains(".")){ + newRecordSet.name.split('.').map(x => x + "." + zone.name) + } else { + Array(newRecordSet.name) + } + for { zone <- zoneRepository.getZoneByName(newRecordFqdn) + allMatchingZones <- zoneRepository.getZonesByFilters(possibleZones.toSet) record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) + isNotTheIntendedZone = allMatchingZones.map(x => x.name).exists(x => newRecordFqdn.contains(x)) isZoneAlreadyExist = zone.isDefined isRecordAlreadyExist = doesRecordWithSameTypeExist(record, newRecordSet) - doesNotExist = if(isZoneAlreadyExist || isRecordAlreadyExist) false else true + doesNotExist = if(isZoneAlreadyExist || isRecordAlreadyExist || isNotTheIntendedZone) false else true } yield doesNotExist } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 6124acefe..4c8e5139f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -103,7 +103,7 @@ object RecordSetValidations { val isDomainAllowed = dottedHostConfig.contains(zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config - if(newRecordSet.name.contains(".") && isDomainAllowed) { + if((newRecordSet.name.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && newRecordSet.name != zone.name) { isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist) } else{ @@ -120,8 +120,8 @@ object RecordSetValidations { ): Either[Throwable, Unit] = ensuring( InvalidRequest( - s"Record with name ${newRecordSet.name} and type ${newRecordSet.typ} already exists. " + - s"Please check the record and zone that's already present and make the change there." + s"Record with fqdn '${newRecordSet.name}.${zone.name}' cannot be created. " + + s"Please check if there's a zone or record that already exist and make the change there." ) )( (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index d071b846d..50d63a36e 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -18,7 +18,7 @@ package vinyldns.api import com.comcast.ip4s.IpAddress import org.joda.time.DateTime -import vinyldns.api.config.{BatchChangeConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.batch.V6DiscoveryNibbleBoundaries import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ @@ -40,6 +40,8 @@ trait VinylDNSTestHelpers { val approvedNameServers: List[Regex] = List(new Regex("some.test.ns.")) + val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List("dotted.xyz")) + val defaultTtl: Long = 7200 val manualReviewDomainList: List[Regex] = List(new Regex("needs-review.*")) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala index fb27338c1..d80caee54 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala @@ -418,7 +418,8 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) private val underTestManualEnabled = new BatchChangeService( @@ -434,7 +435,8 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) private val underTestScheduledEnabled = new BatchChangeService( @@ -450,7 +452,8 @@ class BatchChangeServiceSpec mockNotifiers, true, defaultv6Discovery, - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) "applyBatchChange" should { @@ -476,7 +479,8 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 17), - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) val ptr = AddChangeInput( "2001:0000:0000:0001:0000:ff00:0042:8329", @@ -507,7 +511,8 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 16), - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) val ptr = AddChangeInput( "2001:0000:0000:0001:0000:ff00:0042:8329", @@ -1177,7 +1182,8 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329" @@ -1218,7 +1224,8 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 16), - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329" @@ -1244,7 +1251,8 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L + 7200L, + VinylDNSTestHelpers.dottedHostsConfig ) val ip1 = "::1" diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index f322b2523..576494f3f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -819,7 +819,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -884,7 +886,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.foreach(_ shouldBe valid) @@ -924,7 +928,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.foreach(_ shouldBe valid) @@ -951,7 +957,9 @@ class BatchChangeValidationsSpec ), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -982,7 +990,9 @@ class BatchChangeValidationsSpec ), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1022,7 +1032,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1072,7 +1084,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1110,7 +1124,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.foreach(_ shouldBe valid) @@ -1133,7 +1149,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(newRecordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1150,7 +1168,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1166,7 +1186,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid } @@ -1185,7 +1207,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(existingRecordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1217,7 +1241,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1238,7 +1264,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel), ExistingRecordSets(newRecordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1267,7 +1295,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1292,7 +1322,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel), ExistingRecordSets(List(existingRecordPTR))), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1327,7 +1359,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1362,7 +1396,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1402,7 +1438,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1444,7 +1482,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) @@ -1463,7 +1503,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1482,7 +1524,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), AuthPrincipal(superUser, Seq.empty), false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1509,7 +1553,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1525,7 +1571,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1557,7 +1605,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel, addPtr.validNel), ExistingRecordSets(List())), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1582,7 +1632,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1603,7 +1655,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1633,7 +1687,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1655,7 +1711,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1677,7 +1735,9 @@ class BatchChangeValidationsSpec ), AuthPrincipal(superUser, Seq.empty), false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1708,7 +1768,9 @@ class BatchChangeValidationsSpec ), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1731,7 +1793,9 @@ class BatchChangeValidationsSpec ), notAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError]( @@ -1799,7 +1863,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1848,7 +1914,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) } @@ -1891,7 +1959,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) } @@ -1921,7 +1991,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) } @@ -1959,7 +2031,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -1998,7 +2072,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) } @@ -2031,7 +2107,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) } @@ -2069,7 +2147,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.map(_ shouldBe valid) @@ -2183,7 +2263,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addMX.validNel), ExistingRecordSets(List(existingMX))), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError](RecordAlreadyExists("name-conflict.")) } @@ -2206,7 +2288,9 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addMx.validNel, addMx2.validNel), ExistingRecordSets(List())), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid } @@ -2237,7 +2321,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid } @@ -2266,7 +2352,9 @@ class BatchChangeValidationsSpec ), AuthPrincipal(okUser, Seq(abcGroup.id, okGroup.id)), false, - Some("some-owner-group-id") + Some("some-owner-group-id"), + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result.foreach(_ shouldBe valid) @@ -2296,7 +2384,9 @@ class BatchChangeValidationsSpec ), AuthPrincipal(okUser, Seq(abcGroup.id, okGroup.id)), false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2325,7 +2415,9 @@ class BatchChangeValidationsSpec ), dummyAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should @@ -2349,7 +2441,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2363,7 +2457,9 @@ class BatchChangeValidationsSpec ), sharedAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2397,7 +2493,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - Some(okGroup.id) + Some(okGroup.id), + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2442,7 +2540,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - Some(okGroup.id) + Some(okGroup.id), + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2501,7 +2601,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) should haveInvalid[DomainValidationError](ZoneDiscoveryError("dotted.a.ok.")) @@ -2555,7 +2657,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid @@ -2657,7 +2761,9 @@ class BatchChangeValidationsSpec ), okAuth, false, - None + None, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) result(0) shouldBe valid diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 3d629e43b..34df9e161 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -83,6 +83,7 @@ class RecordSetServiceSpec mockBackendResolver, false, VinylDNSTestHelpers.highValueDomainConfig, + VinylDNSTestHelpers.dottedHostsConfig, VinylDNSTestHelpers.approvedNameServers, true ) @@ -101,6 +102,7 @@ class RecordSetServiceSpec mockBackendResolver, true, VinylDNSTestHelpers.highValueDomainConfig, + VinylDNSTestHelpers.dottedHostsConfig, VinylDNSTestHelpers.approvedNameServers, true ) @@ -165,6 +167,18 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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)) val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) result shouldBe an[InvalidRequest] diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index 304f65ed4..bcbfa747d 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -189,19 +189,19 @@ class RecordSetValidationsSpec val dottedARecord = rsOk.copy(name = "this.is.a.failure.") "return a failure for any new record with dotted hosts in forward zones" in { leftValue( - typeSpecificValidations(dottedARecord, List(), okZone, None, Nil) + typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (CNAME)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil) + typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (NS)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil) + typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) ) shouldBe an[InvalidRequest] } @@ -211,7 +211,9 @@ class RecordSetValidationsSpec List(), okZone, Some(dottedARecord.copy(ttl = 300)), - Nil + Nil, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) should be(right) } @@ -222,7 +224,9 @@ class RecordSetValidationsSpec List(), okZone, Some(dottedCNAMERecord.copy(ttl = 300)), - Nil + Nil, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) should be(right) } @@ -234,7 +238,9 @@ class RecordSetValidationsSpec List(), okZone, Some(dottedNSRecord.copy(ttl = 300)), - Nil + Nil, + true, + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet ) ) shouldBe an[InvalidRequest] } @@ -245,35 +251,35 @@ class RecordSetValidationsSpec val test = srv.copy(name = "_sip._tcp.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success for an SRV record following convention without FQDN" in { val test = srv.copy(name = "_sip._tcp") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success for an SRV record following convention with a record name" in { val test = srv.copy(name = "_sip._tcp.foo.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success on a wildcard SRV that follows convention" in { val test = srv.copy(name = "*._tcp.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success on a wildcard in second position SRV that follows convention" in { val test = srv.copy(name = "_sip._*.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } } "Skip dotted checks on NAPTR" should { @@ -281,21 +287,21 @@ class RecordSetValidationsSpec val test = naptr.copy(name = "sub.naptr.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success for an NAPTR record without FQDN" in { val test = naptr.copy(name = "sub.naptr") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return success on a wildcard NAPTR" in { val test = naptr.copy(name = "*.sub.naptr.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } } @@ -304,7 +310,7 @@ class RecordSetValidationsSpec val test = ptrIp4.copy(name = "10.1.2.") 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } } "Skip dotted checks on TXT" should { @@ -312,7 +318,7 @@ class RecordSetValidationsSpec val test = txt.copy(name = "sub.txt.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } } @@ -329,7 +335,7 @@ class RecordSetValidationsSpec 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } } } @@ -342,29 +348,29 @@ class RecordSetValidationsSpec 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if an NS record is the same as the zone" in { val invalid = invalidNsApexRs.copy(name = okZone.name) - val error = leftValue(nsValidations(invalid, okZone, None, Nil)) + val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the NS record being updated is '@'" in { 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "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 error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")))) + val error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } } @@ -372,25 +378,25 @@ class RecordSetValidationsSpec "DSValidations" should { 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 { - dsValidations(ds, List(matchingNs), okZone) should be(right) + dsValidations(ds, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "return an InvalidRequest if a DS record is '@'" in { val apex = ds.copy(name = "@") - val error = leftValue(dsValidations(apex, List(matchingNs), okZone)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a DS record is the same as the zone" in { val apex = ds.copy(name = okZone.name) - val error = leftValue(dsValidations(apex, List(matchingNs), okZone)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the DS is dotted" in { val error = - leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone)) + leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } } @@ -398,51 +404,51 @@ class RecordSetValidationsSpec "CnameValidations" should { val invalidCnameApexRs: RecordSet = cname.copy(name = "@") "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe a[RecordSetAlreadyExists] } "return ok if name is not '@'" in { - cnameValidations(cname, List(), okZone) should be(right) + cnameValidations(cname, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a cname record set name is same as zone" in { val invalid = invalidCnameApexRs.copy(name = okZone.name) - val error = leftValue(cnameValidations(invalid, List(), okZone)) + val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( right ) } "return ok if dotted host name doesn't change" in { 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( right ) } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if updated cname record set name is same as zone" in { 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[InvalidRequest] } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) error shouldBe an[RecordSetValidation] } "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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( right ) } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 6b56663d1..79998757b 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -109,10 +109,10 @@ final case class ZoneDiscoveryError(name: String, fatal: Boolean = false) "If zone exists, then it must be connected to in VinylDNS." } -final case class DottedHostError(name: String, rsType: String) extends DomainValidationError { +final case class DottedHostError(name: String, zone: String) extends DomainValidationError { def message: String = - s"Record with name $name and type $rsType already exists. " + - s"Please check the record and zone that's already present and make the change there." + s"Record with fqdn '$name.$zone' cannot be created. " + + s"Please check if there's a zone or record that already exist and make the change there." } final case class RecordAlreadyExists(name: String) extends DomainValidationError { diff --git a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala index 751d44359..cdc6cc925 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala @@ -34,6 +34,7 @@ object TestZoneData { adminGroupId = okGroup.id, connection = testConnection ) + val dottedZone: Zone = Zone("dotted.xyz", "dotted@xyz.com", adminGroupId = xyzGroup.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 zoneIp4: Zone = Zone("0.162.198.in-addr.arpa.", "test@test.com", adminGroupId = abcGroup.id) From fe69ae7acfbce269d0cb0a883db910defb04fee8 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 14 Sep 2022 14:07:08 +0530 Subject: [PATCH 04/30] Restrict user access --- .../api/src/main/resources/application.conf | 15 +- modules/api/src/main/resources/reference.conf | 14 +- .../api/config/DottedHostsConfig.scala | 13 +- .../vinyldns/api/config/VinylDNSConfig.scala | 2 +- .../api/domain/batch/BatchChangeService.scala | 31 ++- .../domain/batch/BatchChangeValidations.scala | 23 +- .../api/domain/record/RecordSetService.scala | 30 ++- .../domain/record/RecordSetValidations.scala | 48 +++-- .../domain/record/RecordSetServiceSpec.scala | 200 +++++++++++++++++- .../core/domain/DomainValidationErrors.scala | 2 +- 10 files changed, 313 insertions(+), 65 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 8b7df4d5c..3d717a112 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,11 +165,16 @@ vinyldns { "ns1.parent.com4." ] - # approved zones/domains that are allowed to create dotted hosts - zone-list = [ - "dummy.", # for local testing - "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) - ] + # approved zones/domains and users that are allowed to create dotted hosts + dotted-hosts = { + zone-list = [ + "dummy.", # for local testing + "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) + ] + allowed-user-list = [ + "testuser" # for local testing + ] + } # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production crypto { diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index c60afc5f0..5dec2af5b 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -90,10 +90,16 @@ vinyldns { "ns1.parent.com." ] - # approved zones/domains that are allowed to create dotted hosts - zone-list = [ - "ok." # for local testing - ] + # approved zones/domains and users that are allowed to create dotted hosts + dotted-hosts = { + zone-list = [ + "dummy.", # for local testing + "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) + ] + allowed-user-list = [ + "testuser" # for local testing + ] + } # color should be green or blue, used in order to do blue/green deployment color = "green" diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index e6a375d69..178d0c254 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -18,11 +18,14 @@ package vinyldns.api.config import pureconfig.ConfigReader -final case class DottedHostsConfig(zoneList: List[String]) +final case class DottedHostsConfig(zoneList: List[String], allowedUserList: List[String]) object DottedHostsConfig { implicit val configReader: ConfigReader[DottedHostsConfig] = - ConfigReader.forProduct1[DottedHostsConfig, List[String]]( - "zone-list" - )(zoneList => - DottedHostsConfig(zoneList)) + ConfigReader.forProduct2[DottedHostsConfig, List[String], List[String]]( + "zone-list", + "allowed-user-list" + ){ + case (zoneList, allowedUserList) => + DottedHostsConfig(zoneList, allowedUserList) + } } diff --git a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala index fc5a0d42e..dd41bb3a0 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala @@ -86,7 +86,7 @@ object VinylDNSConfig { serverConfig <- loadIO[ServerConfig](config, "vinyldns") batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns") backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend") - dottedHostsConfig <- loadIO[DottedHostsConfig](config, "vinyldns") + dottedHostsConfig <- loadIO[DottedHostsConfig](config, "vinyldns.dotted-hosts") httpConfig <- loadIO[HttpConfig](config, "vinyldns.rest") hvdConfig <- loadIO[HighValueDomainConfig](config, "vinyldns.high-value-domains") scheduledChangesConfig <- loadIO[ScheduledChangesConfig](config, "vinyldns") diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index d35bcaf16..7c9d07efb 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -127,6 +127,7 @@ class BatchChangeService( withTtl = doTtlMapping(changesWithZones, recordSets) groupedChanges = ChangeForValidationMap(withTtl, recordSets) allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toBatchResult + isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(groupedChanges).toBatchResult validatedSingleChanges = validateChangesWithContext( groupedChanges, @@ -134,7 +135,8 @@ class BatchChangeService( isApproved, batchChangeInput.ownerGroupId, zoneOrRecordDoesNotAlreadyExist, - allowedZoneList + allowedZoneList, + isUserAllowed ) errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges) validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges) @@ -142,14 +144,21 @@ class BatchChangeService( // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not def zoneOrRecordDoesNotExist(groupedChanges: ChangeForValidationMap): IO[Boolean] = { - val inputChange = groupedChanges.changes.map(x => x.toOption).map(x => x.get) - // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist - val newRecordFqdn = inputChange.map(_.recordName).head + "." + inputChange.map(_.zone.name).head + val groupedChangesMap = groupedChanges.changes.map(x => x.toOption).filter(x => x.isDefined) + + val newRecordFqdn = if(groupedChangesMap.nonEmpty){ + val inputChange = groupedChangesMap.map(x => x.get) + // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist + inputChange.map(_.recordName).head + "." + inputChange.map(_.zone.name).head + } else { + "" + } + for { zone <- zoneRepository.getZoneByName(newRecordFqdn) record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) isZoneAlreadyExist = zone.isDefined - isRecordAlreadyExist = doesRecordWithSameTypeExist(record, inputChange.map(_.inputChange)) + isRecordAlreadyExist = if(groupedChangesMap.nonEmpty) doesRecordWithSameTypeExist(record, groupedChangesMap.map(x => x.get).map(_.inputChange)) else false doesNotExist = if(isZoneAlreadyExist || isRecordAlreadyExist) false else true } yield doesNotExist } @@ -176,7 +185,7 @@ class BatchChangeService( 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{ + for { namedZoneResult <- zoneRepository.getZonesByNames(namedZones.toSet) wildcardZoneResult <- zoneRepository.getZonesByFilters(wildcardZones.toSet) zoneResult = namedZoneResult ++ wildcardZoneResult // Combine the zones @@ -184,6 +193,16 @@ class BatchChangeService( } } + // Check if the user is allowed to create dotted hosts using the users present in dotted hosts config + def checkIfAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { + if(users.contains(auth.signedInUser.userName)){ + true + } + else { + false + } + } + def getGroupIdsFromUnauthorizedErrors( changes: ValidatedBatch[ChangeForValidation] ): BatchResult[Set[Group]] = { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 1ac65e91c..4ff99e0de 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -50,7 +50,8 @@ trait BatchChangeValidationsAlgebra { isApproved: Boolean, batchOwnerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): ValidatedBatch[ChangeForValidation] def canGetBatchChange( @@ -273,7 +274,8 @@ class BatchChangeValidations( isApproved: Boolean, batchOwnerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): ValidatedBatch[ChangeForValidation] = // Updates are a combination of an add and delete for a record with the same name and type in a zone. groupedChanges.changes.mapValid { @@ -281,7 +283,7 @@ class BatchChangeValidations( if groupedChanges .getLogicalChangeType(add.recordKey) .contains(LogicalChangeType.Add) => - validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) case addUpdate: AddChangeForValidation => validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId) // These cases MUST be below adds because: @@ -297,15 +299,15 @@ class BatchChangeValidations( } // Check if the new record set has dots and if so whether they are allowed or not - def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: Set[String]): SingleValidation[Unit] = { + def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean): SingleValidation[Unit] = { // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostConfig.contains(change.zone.name) + val isDomainAllowed = dottedHostZoneConfig.contains(change.zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config if((change.recordName.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && change.recordName != change.zone.name) { - // If there is a zone or record already present which conflicts with the new dotted record, throw an error - if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name) + // If the user is not authorized or if there is a zone/record already present which conflicts with the new dotted record, throw an error + if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name || !isUserAllowed) DottedHostError(change.recordName, change.zone.name).invalidNel else ().validNel @@ -429,14 +431,15 @@ class BatchChangeValidations( isApproved: Boolean, ownerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): SingleValidation[ChangeForValidation] = { val typedValidations = change.inputChange.typ match { case A | AAAA | MX => - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig: Set[String]) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig: Set[String]) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed) case TXT | PTR => ().validNel case other => diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 719f2595c..13ef634a8 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -111,6 +111,7 @@ class RecordSetService( _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] + isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -119,7 +120,8 @@ class RecordSetService( None, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, - allowedZoneList + allowedZoneList, + isUserAllowed ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -151,6 +153,7 @@ class RecordSetService( ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] + isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -159,7 +162,8 @@ class RecordSetService( Some(existing), approvedNameServers, zoneOrRecordDoesNotAlreadyExist, - allowedZoneList + allowedZoneList, + isUserAllowed ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -185,18 +189,12 @@ class RecordSetService( // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist val newRecordFqdn = if(newRecordSet.name != zone.name) newRecordSet.name + "." + zone.name else newRecordSet.name - val possibleZones = if(newRecordSet.name.contains(".")){ - newRecordSet.name.split('.').map(x => x + "." + zone.name) - } else { - Array(newRecordSet.name) - } - for { - zone <- zoneRepository.getZoneByName(newRecordFqdn) - allMatchingZones <- zoneRepository.getZonesByFilters(possibleZones.toSet) + existingZone <- zoneRepository.getZoneByName(newRecordFqdn) + allMatchingZones <- if(newRecordSet.name.contains(".")) zoneRepository.getZonesByFilters(newRecordSet.name.split('.').map(x => x + "." + zone.name).toSet) else zoneRepository.getZonesByFilters(Set.empty) record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) isNotTheIntendedZone = allMatchingZones.map(x => x.name).exists(x => newRecordFqdn.contains(x)) - isZoneAlreadyExist = zone.isDefined + isZoneAlreadyExist = existingZone.isDefined isRecordAlreadyExist = doesRecordWithSameTypeExist(record, newRecordSet) doesNotExist = if(isZoneAlreadyExist || isRecordAlreadyExist || isNotTheIntendedZone) false else true } yield doesNotExist @@ -232,6 +230,16 @@ class RecordSetService( } } + // Check if user is allowed to create dotted hosts using the users present in dotted hosts config + def checkIfAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { + if(users.contains(auth.signedInUser.userName)){ + true + } + else { + false + } + } + def getRecordSet( recordSetId: String, authPrincipal: AuthPrincipal diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 4c8e5139f..92a2e051a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -96,35 +96,37 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): Either[Throwable, Unit] = { // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostConfig.contains(zone.name) + val isDomainAllowed = dottedHostZoneConfig.contains(zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config if((newRecordSet.name.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && newRecordSet.name != zone.name) { - isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist) + isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, isUserAllowed) } else{ isNotDotted(newRecordSet, zone, existingRecordSet) } } - // Check if there is a zone or record already present which conflicts with the new dotted record. If so, throw an error + // Check if the user is not authorized and if there is a zone/record already present which conflicts with the new dotted record. If so, throw an error def isDotted( newRecordSet: RecordSet, zone: Zone, existingRecordSet: Option[RecordSet] = None, - zoneOrRecordDoesNotAlreadyExist: Boolean + zoneOrRecordDoesNotAlreadyExist: Boolean, + isUserAllowed: Boolean ): Either[Throwable, Unit] = ensuring( InvalidRequest( s"Record with fqdn '${newRecordSet.name}.${zone.name}' cannot be created. " + - s"Please check if there's a zone or record that already exist and make the change there." + s"Please check if user has permission or if there's a zone/record that already exist and make the change there." ) )( - (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist + (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist && isUserAllowed ) // Check if the recordset contains dot but is not in the allowed zones to create dotted records. If so, throw an error @@ -150,16 +152,17 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet], approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean = true, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): Either[Throwable, Unit] = newRecordSet.typ match { - case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) - case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) - case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) case PTR => ptrValidations(newRecordSet, zone) case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check - case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) - case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) } def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = @@ -182,7 +185,8 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): Either[Throwable, Unit] = { // cannot create a cname record if a record with the same exists val noRecordWithName = { @@ -215,7 +219,7 @@ object RecordSetValidations { ) _ <- noRecordWithName _ <- RDataWithConsecutiveDots - _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) } yield () } @@ -225,7 +229,8 @@ object RecordSetValidations { existingRecordsWithName: List[RecordSet], zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): Either[Throwable, Unit] = { // see https://tools.ietf.org/html/rfc4035#section-2.4 val nsChecks = existingRecordsWithName.find(_.typ == NS) match { @@ -238,7 +243,7 @@ object RecordSetValidations { } for { - _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) + _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) _ <- isNotOrigin( newRecordSet, zone, @@ -254,10 +259,11 @@ object RecordSetValidations { oldRecordSet: Option[RecordSet], approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostConfig: Set[String] + dottedHostZoneConfig: Set[String], + isUserAllowed: Boolean ): Either[Throwable, Unit] = { // TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically - val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight + val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) else ().asRight for { _ <- isNotDottedHost @@ -279,9 +285,9 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostConfig: Set[String]): Either[Throwable, Unit] = + def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean): Either[Throwable, Unit] = // TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here - if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostConfig) else ().asRight + if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) else ().asRight def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = // TODO we don't check for PTR as dotted...not sure why diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 34df9e161..52bd78588 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -117,6 +117,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( @@ -157,7 +172,7 @@ class RecordSetServiceSpec val result = leftResultOf(underTest.addRecordSet(aaaa, okAuth).value) result shouldBe a[RecordSetAlreadyExists] } - "fail if the record is dotted" in { + "fail if the record is dotted and zone is not in the dotted hosts config allowed zones" in { val record = aaaa.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active) @@ -179,6 +194,9 @@ class RecordSetServiceSpec 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) val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) result shouldBe an[InvalidRequest] @@ -193,6 +211,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result = leftResultOf(underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value) @@ -218,6 +251,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -236,6 +284,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -273,6 +336,21 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(okGroup))) .when(mockGroupRepo) .getGroup(okGroup.id) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -326,6 +404,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( @@ -355,6 +448,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -391,6 +499,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -430,6 +553,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -452,6 +590,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -474,6 +627,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -609,6 +777,21 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(oneUserDummyGroup))) .when(mockGroupRepo) .getGroup(oneUserDummyGroup.id) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result = rightResultOf( underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value @@ -638,6 +821,21 @@ class RecordSetServiceSpec doReturn(IO.pure(List(oldRecord))) .when(mockRecordRepo) .getRecordSetsByName(zone.id, newRecord.name) + doReturn(IO.pure(Set(dottedZone))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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) val result = rightResultOf( underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 79998757b..457fbfc27 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -112,7 +112,7 @@ final case class ZoneDiscoveryError(name: String, fatal: Boolean = false) final case class DottedHostError(name: String, zone: String) extends DomainValidationError { def message: String = s"Record with fqdn '$name.$zone' cannot be created. " + - s"Please check if there's a zone or record that already exist and make the change there." + s"Please check if user has permission or if there's a zone/record that already exist and make the change there." } final case class RecordAlreadyExists(name: String) extends DomainValidationError { From 2544d821cd6c426c9d359ac428e369632b0d93f4 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 15 Sep 2022 17:44:41 +0530 Subject: [PATCH 05/30] Restrict record type --- .../api/src/main/resources/application.conf | 15 +++++-- modules/api/src/main/resources/reference.conf | 6 +++ .../api/config/DottedHostsConfig.scala | 12 +++--- .../api/domain/batch/BatchChangeService.scala | 22 ++++++++-- .../domain/batch/BatchChangeValidations.scala | 23 ++++++----- .../api/domain/record/RecordSetService.scala | 29 +++++++++++--- .../domain/record/RecordSetValidations.scala | 40 +++++++++---------- .../domain/membership/GroupRepository.scala | 2 + .../repository/MySqlGroupRepository.scala | 28 +++++++++++++ 9 files changed, 131 insertions(+), 46 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 3d717a112..f9f6cf6e3 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,14 +165,23 @@ vinyldns { "ns1.parent.com4." ] - # approved zones/domains and users that are allowed to create dotted hosts + # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { zone-list = [ "dummy.", # for local testing - "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) + "*ent.com" # for local testing with wildcard zones (allows parent.com and child.parent.com zones) ] allowed-user-list = [ - "testuser" # for local testing + "testuser", + "professor" # for local testing (username) + ] + allowed-group-list = [ + "test-group", + "duGroup" # for local testing (group name) + ] + allowed-record-type = [ + "A", + "CNAME" # for local testing ] } diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 5dec2af5b..e83541a12 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -99,6 +99,12 @@ vinyldns { allowed-user-list = [ "testuser" # for local testing ] + allowed-group-list = [ + "test-group" # for local testing + ] + allowed-record-type = [ + "A" # for local testing + ] } # color should be green or blue, used in order to do blue/green deployment diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index 178d0c254..2a5051976 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -18,14 +18,16 @@ package vinyldns.api.config import pureconfig.ConfigReader -final case class DottedHostsConfig(zoneList: List[String], allowedUserList: List[String]) +final case class DottedHostsConfig(zoneList: List[String], allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) object DottedHostsConfig { implicit val configReader: ConfigReader[DottedHostsConfig] = - ConfigReader.forProduct2[DottedHostsConfig, List[String], List[String]]( + ConfigReader.forProduct4[DottedHostsConfig, List[String], List[String], List[String], List[String]]( "zone-list", - "allowed-user-list" + "allowed-user-list", + "allowed-group-list", + "allowed-record-type" ){ - case (zoneList, allowedUserList) => - DottedHostsConfig(zoneList, allowedUserList) + case (zoneList, allowedUserList, allowedGroupList, allowedRecordType) => + DottedHostsConfig(zoneList, allowedUserList, allowedGroupList, allowedRecordType) } } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index 7c9d07efb..b38d45fe2 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -127,7 +127,9 @@ class BatchChangeService( withTtl = doTtlMapping(changesWithZones, recordSets) groupedChanges = ChangeForValidationMap(withTtl, recordSets) allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toBatchResult - isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toBatchResult + isAllowedUser = isInAllowedUsers || isUserInAllowedGroups zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(groupedChanges).toBatchResult validatedSingleChanges = validateChangesWithContext( groupedChanges, @@ -136,7 +138,8 @@ class BatchChangeService( batchChangeInput.ownerGroupId, zoneOrRecordDoesNotAlreadyExist, allowedZoneList, - isUserAllowed + isAllowedUser, + dottedHostsConfig ) errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges) validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges) @@ -193,8 +196,8 @@ class BatchChangeService( } } - // Check if the user is allowed to create dotted hosts using the users present in dotted hosts config - def checkIfAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { + // Check if user is allowed to create dotted hosts using the users present in dotted hosts config + def checkIfInAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { if(users.contains(auth.signedInUser.userName)){ true } @@ -203,6 +206,17 @@ class BatchChangeService( } } + // Check if user is allowed to create dotted hosts using the groups present in dotted hosts config + def checkIfInAllowedGroups(groups: List[String], auth: AuthPrincipal): IO[Boolean] = { + for{ + groupsInConfig <- groupRepository.getGroupsByName(groups.toSet) + members = groupsInConfig.flatMap(x => x.memberIds) + usersList <- userRepository.getUsers(members, None, None) + users = usersList.users.map(x => x.userName) + isPresent = users.contains(auth.signedInUser.userName) + } yield isPresent + } + def getGroupIdsFromUnauthorizedErrors( changes: ValidatedBatch[ChangeForValidation] ): BatchResult[Set[Group]] = { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 4ff99e0de..fd1eac402 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -19,7 +19,7 @@ package vinyldns.api.domain.batch import java.net.InetAddress import cats.data._ import cats.implicits._ -import vinyldns.api.config.{BatchChangeConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.DomainValidations._ import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.auth.AuthPrincipal @@ -51,7 +51,8 @@ trait BatchChangeValidationsAlgebra { batchOwnerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isUserAllowed: Boolean, + dottedHostsConfig: DottedHostsConfig ): ValidatedBatch[ChangeForValidation] def canGetBatchChange( @@ -275,7 +276,8 @@ class BatchChangeValidations( batchOwnerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isUserAllowed: Boolean, + dottedHostsConfig: DottedHostsConfig ): ValidatedBatch[ChangeForValidation] = // Updates are a combination of an add and delete for a record with the same name and type in a zone. groupedChanges.changes.mapValid { @@ -283,7 +285,7 @@ class BatchChangeValidations( if groupedChanges .getLogicalChangeType(add.recordKey) .contains(LogicalChangeType.Add) => - validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed, dottedHostsConfig) case addUpdate: AddChangeForValidation => validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId) // These cases MUST be below adds because: @@ -299,13 +301,15 @@ class BatchChangeValidations( } // Check if the new record set has dots and if so whether they are allowed or not - def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean): SingleValidation[Unit] = { + def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean, dottedHostsConfig: DottedHostsConfig): SingleValidation[Unit] = { // Check if the zone of the record set is present in dotted hosts config list val isDomainAllowed = dottedHostZoneConfig.contains(change.zone.name) + val isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(change.inputChange.typ.toString) + // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config - if((change.recordName.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && change.recordName != change.zone.name) { + if((change.recordName.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && isUserAllowed && isRecordTypeAllowed && change.recordName != change.zone.name) { // If the user is not authorized or if there is a zone/record already present which conflicts with the new dotted record, throw an error if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name || !isUserAllowed) DottedHostError(change.recordName, change.zone.name).invalidNel @@ -432,14 +436,15 @@ class BatchChangeValidations( ownerGroupId: Option[String], zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isUserAllowed: Boolean, + dottedHostsConfig: DottedHostsConfig ): SingleValidation[ChangeForValidation] = { val typedValidations = change.inputChange.typ match { case A | AAAA | MX => - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed, dottedHostsConfig) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed) + newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed, dottedHostsConfig) case TXT | PTR => ().validNel case other => diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 13ef634a8..897ff5f6c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -111,7 +111,11 @@ class RecordSetService( _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] - isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toResult[Boolean] + isAllowedUser = isInAllowedUsers || isUserInAllowedGroups + isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) + isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -121,7 +125,7 @@ class RecordSetService( approvedNameServers, zoneOrRecordDoesNotAlreadyExist, allowedZoneList, - isUserAllowed + isRecordTypeAndUserAllowed ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -153,7 +157,11 @@ class RecordSetService( ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] - isUserAllowed = checkIfAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) + isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toResult[Boolean] + isAllowedUser = isInAllowedUsers || isUserInAllowedGroups + isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) + isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -163,7 +171,7 @@ class RecordSetService( approvedNameServers, zoneOrRecordDoesNotAlreadyExist, allowedZoneList, - isUserAllowed + isRecordTypeAndUserAllowed, ).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -231,7 +239,7 @@ class RecordSetService( } // Check if user is allowed to create dotted hosts using the users present in dotted hosts config - def checkIfAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { + def checkIfInAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { if(users.contains(auth.signedInUser.userName)){ true } @@ -240,6 +248,17 @@ class RecordSetService( } } + // Check if user is allowed to create dotted hosts using the groups present in dotted hosts config + def checkIfInAllowedGroups(groups: List[String], auth: AuthPrincipal): IO[Boolean] = { + for{ + groupsInConfig <- groupRepository.getGroupsByName(groups.toSet) + members = groupsInConfig.flatMap(x => x.memberIds) + usersList <- userRepository.getUsers(members, None, None) + users = usersList.users.map(x => x.userName) + isPresent = users.contains(auth.signedInUser.userName) + } yield isPresent + } + def getRecordSet( recordSetId: String, authPrincipal: AuthPrincipal diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 92a2e051a..1fe942aa0 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -97,17 +97,17 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { // Check if the zone of the record set is present in dotted hosts config list val isDomainAllowed = dottedHostZoneConfig.contains(zone.name) // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config - if((newRecordSet.name.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && newRecordSet.name != zone.name) { - isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, isUserAllowed) + if((newRecordSet.name.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && isRecordTypeAndUserAllowed && newRecordSet.name != zone.name) { + isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, isRecordTypeAndUserAllowed) } - else{ + else { isNotDotted(newRecordSet, zone, existingRecordSet) } } @@ -118,7 +118,7 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = ensuring( InvalidRequest( @@ -126,7 +126,7 @@ object RecordSetValidations { s"Please check if user has permission or if there's a zone/record that already exist and make the change there." ) )( - (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist && isUserAllowed + (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist && isRecordTypeAndUserAllowed ) // Check if the recordset contains dot but is not in the allowed zones to create dotted records. If so, throw an error @@ -153,16 +153,16 @@ object RecordSetValidations { approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean = true, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = newRecordSet.typ match { - case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) - case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) - case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) case PTR => ptrValidations(newRecordSet, zone) case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check - case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) - case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) } def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = @@ -186,7 +186,7 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet] = None, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { // cannot create a cname record if a record with the same exists val noRecordWithName = { @@ -219,7 +219,7 @@ object RecordSetValidations { ) _ <- noRecordWithName _ <- RDataWithConsecutiveDots - _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) } yield () } @@ -230,7 +230,7 @@ object RecordSetValidations { zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { // see https://tools.ietf.org/html/rfc4035#section-2.4 val nsChecks = existingRecordsWithName.find(_.typ == NS) match { @@ -243,7 +243,7 @@ object RecordSetValidations { } for { - _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) + _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) _ <- isNotOrigin( newRecordSet, zone, @@ -260,10 +260,10 @@ object RecordSetValidations { approvedNameServers: List[Regex], zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { // TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically - val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) else ().asRight + val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight for { _ <- isNotDottedHost @@ -285,9 +285,9 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean): Either[Throwable, Unit] = + def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean): Either[Throwable, Unit] = // TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here - if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed) else ().asRight + if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = // TODO we don't check for PTR as dotted...not sure why diff --git a/modules/core/src/main/scala/vinyldns/core/domain/membership/GroupRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/membership/GroupRepository.scala index 233a2f6b5..9532bd6a4 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/membership/GroupRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/membership/GroupRepository.scala @@ -31,6 +31,8 @@ trait GroupRepository extends Repository { def getGroups(groupIds: Set[String]): IO[Set[Group]] + def getGroupsByName(groupNames: Set[String]): IO[Set[Group]] + def getGroupByName(groupName: String): IO[Option[Group]] def getAllGroups(): IO[Set[Group]] diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlGroupRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlGroupRepository.scala index 83ead82f7..1ba0bdf08 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlGroupRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlGroupRepository.scala @@ -69,6 +69,13 @@ class MySqlGroupRepository extends GroupRepository with GroupProtobufConversions | WHERE id """.stripMargin + private val BASE_GET_GROUPS_BY_NAMES = + """ + |SELECT data + | FROM groups + | WHERE name + """.stripMargin + def save(db: DB, group: Group): IO[Group] = monitor("repo.Group.save") { 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]] = monitor("repo.Group.getGroupByName") { IO { From 231a30a9231a65e6e0e207555c799a17bfe75df8 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 16 Sep 2022 13:03:12 +0530 Subject: [PATCH 06/30] Resolve existing tests --- .../api/domain/batch/BatchChangeService.scala | 4 +- .../api/domain/record/RecordSetService.scala | 4 +- .../vinyldns/api/VinylDNSTestHelpers.scala | 2 +- .../batch/BatchChangeValidationsSpec.scala | 212 +++++++++++++----- .../domain/record/RecordSetServiceSpec.scala | 84 +++++++ .../record/RecordSetValidationsSpec.scala | 79 +++---- .../api/repository/EmptyRepositories.scala | 2 + 7 files changed, 291 insertions(+), 96 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index b38d45fe2..f10094d3a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -211,8 +211,8 @@ class BatchChangeService( for{ groupsInConfig <- groupRepository.getGroupsByName(groups.toSet) members = groupsInConfig.flatMap(x => x.memberIds) - usersList <- userRepository.getUsers(members, None, None) - users = usersList.users.map(x => x.userName) + 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 } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 897ff5f6c..52675be88 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -253,8 +253,8 @@ class RecordSetService( for{ groupsInConfig <- groupRepository.getGroupsByName(groups.toSet) members = groupsInConfig.flatMap(x => x.memberIds) - usersList <- userRepository.getUsers(members, None, None) - users = usersList.users.map(x => x.userName) + 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 } diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index 50d63a36e..70ec98f27 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -40,7 +40,7 @@ trait VinylDNSTestHelpers { val approvedNameServers: List[Regex] = List(new Regex("some.test.ns.")) - val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List("dotted.xyz")) + val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List("dotted.xyz"), List("super"), List("dummy"), List("CNAME")) val defaultTtl: Long = 7200 diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 576494f3f..43bad4d9f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -821,7 +821,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -888,7 +890,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.foreach(_ shouldBe valid) @@ -930,7 +934,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.foreach(_ shouldBe valid) @@ -959,7 +965,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -992,7 +1000,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1034,7 +1044,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1086,7 +1098,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1126,7 +1140,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.foreach(_ shouldBe valid) @@ -1151,7 +1167,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1170,7 +1188,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1188,7 +1208,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid } @@ -1209,7 +1231,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1243,7 +1267,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1266,7 +1292,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1297,7 +1325,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1324,7 +1354,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1361,7 +1393,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1398,7 +1432,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1440,7 +1476,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1484,7 +1522,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) @@ -1505,7 +1545,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1526,7 +1568,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1555,7 +1599,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1573,7 +1619,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1607,7 +1655,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1634,7 +1684,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1657,7 +1709,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1689,7 +1743,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1713,7 +1769,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1737,7 +1795,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1770,7 +1830,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1795,7 +1857,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError]( @@ -1865,7 +1929,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -1916,7 +1982,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) } @@ -1961,7 +2029,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) } @@ -1993,7 +2063,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) } @@ -2033,7 +2105,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2074,7 +2148,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) } @@ -2109,7 +2185,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) } @@ -2149,7 +2227,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.map(_ shouldBe valid) @@ -2265,7 +2345,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError](RecordAlreadyExists("name-conflict.")) } @@ -2290,7 +2372,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid } @@ -2323,7 +2407,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid } @@ -2354,7 +2440,9 @@ class BatchChangeValidationsSpec false, Some("some-owner-group-id"), true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result.foreach(_ shouldBe valid) @@ -2386,7 +2474,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2417,7 +2507,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should @@ -2443,7 +2535,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2459,7 +2553,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2495,7 +2591,9 @@ class BatchChangeValidationsSpec false, Some(okGroup.id), true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2542,7 +2640,9 @@ class BatchChangeValidationsSpec false, Some(okGroup.id), true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2603,7 +2703,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) should haveInvalid[DomainValidationError](ZoneDiscoveryError("dotted.a.ok.")) @@ -2659,7 +2761,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid @@ -2763,7 +2867,9 @@ class BatchChangeValidationsSpec false, None, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false, + VinylDNSTestHelpers.dottedHostsConfig ) result(0) shouldBe valid diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 52bd78588..23cb9e161 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -132,6 +132,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( @@ -197,6 +203,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) result shouldBe an[InvalidRequest] @@ -226,6 +238,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result = leftResultOf(underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value) @@ -266,6 +284,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -299,6 +323,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -351,6 +381,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -419,6 +455,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( @@ -463,6 +505,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -514,6 +562,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -568,6 +622,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -605,6 +665,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -642,6 +708,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result: RecordSetChange = rightResultOf( underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value @@ -792,6 +864,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result = rightResultOf( underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value @@ -836,6 +914,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) + doReturn(IO.pure(Set())) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(Set.empty, None, None) val result = rightResultOf( underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index bcbfa747d..045a69512 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -189,19 +189,19 @@ class RecordSetValidationsSpec val dottedARecord = rsOk.copy(name = "this.is.a.failure.") "return a failure for any new record with dotted hosts in forward zones" in { leftValue( - typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (CNAME)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (NS)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) ) shouldBe an[InvalidRequest] } @@ -213,7 +213,8 @@ class RecordSetValidationsSpec Some(dottedARecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false ) should be(right) } @@ -226,7 +227,8 @@ class RecordSetValidationsSpec Some(dottedCNAMERecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false ) should be(right) } @@ -240,7 +242,8 @@ class RecordSetValidationsSpec Some(dottedNSRecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet + VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + false ) ) shouldBe an[InvalidRequest] } @@ -251,35 +254,35 @@ class RecordSetValidationsSpec val test = srv.copy(name = "_sip._tcp.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success for an SRV record following convention without FQDN" in { val test = srv.copy(name = "_sip._tcp") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success for an SRV record following convention with a record name" in { val test = srv.copy(name = "_sip._tcp.foo.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success on a wildcard SRV that follows convention" in { val test = srv.copy(name = "*._tcp.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success on a wildcard in second position SRV that follows convention" in { val test = srv.copy(name = "_sip._*.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } } "Skip dotted checks on NAPTR" should { @@ -287,21 +290,21 @@ class RecordSetValidationsSpec val test = naptr.copy(name = "sub.naptr.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success for an NAPTR record without FQDN" in { val test = naptr.copy(name = "sub.naptr") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return success on a wildcard NAPTR" in { val test = naptr.copy(name = "*.sub.naptr.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } } @@ -310,7 +313,7 @@ class RecordSetValidationsSpec val test = ptrIp4.copy(name = "10.1.2.") val zone = zoneIp4.copy(name = "198.in-addr.arpa.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } } "Skip dotted checks on TXT" should { @@ -318,7 +321,7 @@ class RecordSetValidationsSpec val test = txt.copy(name = "sub.txt.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } } @@ -335,7 +338,7 @@ class RecordSetValidationsSpec List(SOAData(Fqdn("something"), "other", 1, 2, 3, 5, 6)) ) - typeSpecificValidations(test, List(), zoneIp4, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + typeSpecificValidations(test, List(), zoneIp4, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } } } @@ -348,29 +351,29 @@ class RecordSetValidationsSpec records = List(NSData(Fqdn("some.test.ns."))) ) - nsValidations(valid, okZone, None, List(new Regex(".*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + nsValidations(valid, okZone, None, List(new Regex(".*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return an InvalidRequest if an NS record is '@'" in { - val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if an NS record is the same as the zone" in { val invalid = invalidNsApexRs.copy(name = okZone.name) - val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the NS record being updated is '@'" in { val valid = invalidNsApexRs.copy(name = "this-is-not-origin-mate") - val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "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 error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } } @@ -378,25 +381,25 @@ class RecordSetValidationsSpec "DSValidations" should { 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 { - dsValidations(ds, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + dsValidations(ds, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return an InvalidRequest if a DS record is '@'" in { val apex = ds.copy(name = "@") - val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a DS record is the same as the zone" in { val apex = ds.copy(name = okZone.name) - val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if there is no NS matching the record" in { - val error = leftValue(dsValidations(ds, List(), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(dsValidations(ds, List(), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the DS is dotted" in { val error = - leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } } @@ -404,51 +407,51 @@ class RecordSetValidationsSpec "CnameValidations" should { val invalidCnameApexRs: RecordSet = cname.copy(name = "@") "return a RecordSetAlreadyExistsError if a record with the same name exists and creating a cname" in { - val error = leftValue(cnameValidations(cname, List(aaaa), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(cname, List(aaaa), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe a[RecordSetAlreadyExists] } "return ok if name is not '@'" in { - cnameValidations(cname, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be(right) + cnameValidations(cname, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) } "return an InvalidRequest if a cname record set name is '@'" in { - val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a cname record set name is same as zone" in { val invalid = invalidCnameApexRs.copy(name = okZone.name) - val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a cname record set name is dotted" in { - val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return ok if new recordset name does not contain dot" in { - cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( + cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( right ) } "return ok if dotted host name doesn't change" in { val newRecord = cname.copy(name = "dot.ted", ttl = 500) - cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300)), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( + cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300)), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( right ) } "return an InvalidRequest if a cname record set name is updated to '@'" in { - val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if updated cname record set name is same as zone" in { val error = - leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[InvalidRequest] } "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, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet)) + val error = leftValue(cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record..zone")))), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) error shouldBe an[RecordSetValidation] } "return ok if recordset data does not contain sequential '.'" in { - cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) should be( + cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( right ) } diff --git a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala index a1239cc71..ee466aca3 100644 --- a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala +++ b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala @@ -111,6 +111,8 @@ trait EmptyGroupRepo extends GroupRepository { 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 getAllGroups(): IO[Set[Group]] = IO.pure(Set()) From c59f31f25d09a683eb5ac240ee30990ef8c78843 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Sep 2022 12:21:02 +0530 Subject: [PATCH 07/30] Add tests --- .../api/src/main/resources/application.conf | 2 +- modules/api/src/main/resources/reference.conf | 11 +- .../api/domain/record/RecordSetService.scala | 16 +- .../tests/recordsets/create_recordset_test.py | 32 +++- .../domain/record/RecordSetServiceSpec.scala | 153 +++++++++++++++++- .../record/RecordSetValidationsSpec.scala | 65 ++++++++ .../vinyldns/core/TestMembershipData.scala | 1 + .../MySqlGroupRepositoryIntegrationSpec.scala | 18 +++ 8 files changed, 275 insertions(+), 23 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index f9f6cf6e3..c0f51b0ce 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -168,7 +168,7 @@ vinyldns { # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { zone-list = [ - "dummy.", # for local testing + "dummy", # for local testing "*ent.com" # for local testing with wildcard zones (allows parent.com and child.parent.com zones) ] allowed-user-list = [ diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index e83541a12..a4d8b19d7 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -90,20 +90,19 @@ vinyldns { "ns1.parent.com." ] - # approved zones/domains and users that are allowed to create dotted hosts + # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { zone-list = [ - "dummy.", # for local testing - "*ent.com", # for local testing with wildcard zones (allows parent.com and child.parent.com zone) + "parent.com*" # for local testing with wildcard zones (allows parent.com and child.parent.com zones) ] allowed-user-list = [ - "testuser" # for local testing + "ok" # for local testing (username) ] allowed-group-list = [ - "test-group" # for local testing + "dummy-group*" # for local testing (group name) ] allowed-record-type = [ - "A" # for local testing + "CNAME" # for local testing ] } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 52675be88..ef118e604 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -116,7 +116,7 @@ class RecordSetService( isAllowedUser = isInAllowedUsers || isUserInAllowedGroups isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed - zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] + zoneOrRecordDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, @@ -162,7 +162,7 @@ class RecordSetService( isAllowedUser = isInAllowedUsers || isUserInAllowedGroups isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed - zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(rsForValidations, zone).toResult[Boolean] + zoneOrRecordDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, @@ -192,19 +192,15 @@ class RecordSetService( _ <- messageQueue.send(change).toResult[Unit] } yield change - // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not - def zoneOrRecordDoesNotExist(newRecordSet: RecordSet, zone: Zone): IO[Boolean] = { - // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist + // 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 { - existingZone <- zoneRepository.getZoneByName(newRecordFqdn) - allMatchingZones <- if(newRecordSet.name.contains(".")) zoneRepository.getZonesByFilters(newRecordSet.name.split('.').map(x => x + "." + zone.name).toSet) else zoneRepository.getZonesByFilters(Set.empty) record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) - isNotTheIntendedZone = allMatchingZones.map(x => x.name).exists(x => newRecordFqdn.contains(x)) - isZoneAlreadyExist = existingZone.isDefined isRecordAlreadyExist = doesRecordWithSameTypeExist(record, newRecordSet) - doesNotExist = if(isZoneAlreadyExist || isRecordAlreadyExist || isNotTheIntendedZone) false else true + doesNotExist = if(isRecordAlreadyExist) false else true } yield doesNotExist } diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index c86d0e6a7..b909627e9 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -582,6 +582,25 @@ def test_create_dotted_a_record_apex_with_trailing_dot_succeeds(shared_zone_test def test_create_dotted_cname_record_fails(shared_zone_test_context): + """ + Test that creating a CNAME record set with dotted host record name returns an error. + """ + client = shared_zone_test_context.dummy_vinyldns_client + zone = shared_zone_test_context.dummy_zone + apex_cname_rs = { + "zoneId": zone["id"], + "name": "dot.ted", + "type": "CNAME", + "ttl": 500, + "records": [{"cname": "foo.bar."}] + } + + error = client.create_recordset(apex_cname_rs, 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"]}')) + + +def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfies(shared_zone_test_context): """ Test that creating a CNAME record set with dotted host record name returns an error. """ @@ -595,8 +614,15 @@ def test_create_dotted_cname_record_fails(shared_zone_test_context): "records": [{"cname": "foo.bar."}] } - error = client.create_recordset(apex_cname_rs, 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"]}')) + apex_cname_record = None + try: + apex_cname_response = client.create_recordset(apex_cname_rs, status=202) + apex_cname_record = client.wait_until_recordset_change_status(apex_cname_response, "Complete")["recordSet"] + assert_that(apex_cname_record["name"], is_(apex_cname_rs["name"])) + finally: + if apex_cname_record: + delete_result = client.delete_recordset(apex_cname_record["zoneId"], apex_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): @@ -1368,7 +1394,6 @@ def test_at_create_recordset(shared_zone_test_context): } result = client.create_recordset(new_rs, status=202) - assert_that(result["changeType"], is_("Create")) assert_that(result["status"], is_("Pending")) assert_that(result["created"], is_not(none())) @@ -1418,7 +1443,6 @@ def test_create_record_with_escape_characters_in_record_data_succeeds(shared_zon } result = client.create_recordset(new_rs, status=202) - assert_that(result["changeType"], is_("Create")) assert_that(result["status"], is_("Pending")) assert_that(result["created"], is_not(none())) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 23cb9e161..63ee4ccf0 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -155,7 +155,6 @@ class RecordSetServiceSpec val result = leftResultOf(underTest.getRecordSetByZone(aaaa.id, mockZone.id, okAuth).value) result shouldBe a[ZoneNotFoundError] } - "fail when the account is not authorized" in { doReturn(IO.pure(Some(aaaa))) .when(mockRecordRepo) @@ -178,7 +177,7 @@ class RecordSetServiceSpec val result = leftResultOf(underTest.addRecordSet(aaaa, okAuth).value) result shouldBe a[RecordSetAlreadyExists] } - "fail if the record is dotted and zone is not in the dotted hosts config allowed zones" in { + "fail if the record is dotted and does not satisfy properties in dotted hosts config" in { val record = aaaa.copy(name = "new.name", zoneId = okZone.id, status = RecordSetStatus.Active) @@ -475,6 +474,156 @@ class RecordSetServiceSpec 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) + + 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))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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(xyzGroup))) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.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 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))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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(xyzGroup))) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None))) + .when(mockUserRepo) + .getUsers(xyzGroup.memberIds, 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 = dottedZone.id, status = RecordSetStatus.Active) + + 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))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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(xyzGroup))) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) + .when(mockUserRepo) + .getUsers(xyzGroup.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, xyzAuth).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) + + 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))) + .when(mockZoneRepo) + .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.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(xyzGroup))) + .when(mockGroupRepo) + .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None))) + .when(mockUserRepo) + .getUsers(xyzGroup.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 { "return the recordSet change as the result" in { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index 045a69512..57625b9c1 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -184,6 +184,36 @@ 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 a failure for a dotted record name that matches the zone name" in { + val test = aaaa.copy(name = "ok.www.comcast.net") + val zone = okZone.copy(name = "ok.www.comcast.net") + leftValue(isDotted(test, zone, None, true, true)) 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 { "Run dotted hosts checks" should { val dottedARecord = rsOk.copy(name = "this.is.a.failure.") @@ -205,6 +235,21 @@ class RecordSetValidationsSpec ) shouldBe an[InvalidRequest] } + "return a success for any new record with dotted hosts in forward zones if it satisfies dotted hosts configs" in { + val record = typeSpecificValidations(dottedARecord.copy(zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + record should be(right) + } + + "return a failure for any new record with dotted hosts in forward zones (CNAME) if it satisfies dotted hosts configs" in { + val record = typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + record should be(right) + } + + "return a failure for any new record with dotted hosts in forward zones (NS) if it satisfies dotted hosts configs" in { + val record = typeSpecificValidations(dottedARecord.copy(typ = NS, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + record should be(right) + } + "return a success for any existing record with dotted hosts in forward zones" in { typeSpecificValidations( dottedARecord, @@ -402,6 +447,16 @@ class RecordSetValidationsSpec leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true)) + error shouldBe an[InvalidRequest] + } } "CnameValidations" should { @@ -455,6 +510,16 @@ class RecordSetValidationsSpec 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true)) + error shouldBe an[InvalidRequest] + } } "isNotHighValueDomain" should { diff --git a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala index 36ba54ec0..641b4db1e 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala @@ -36,6 +36,7 @@ object TestMembershipData { val dummyUser = User("dummyName", "dummyAccess", "dummySecret") val superUser = User("super", "superAccess", "superSecret", isSuper = true) + val xyzUser = User("xyz", "xyzAccess", "xyzSecret") val supportUser = User("support", "supportAccess", "supportSecret", isSupport = true) val lockedUser = User("locked", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked) val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", "sharedSecret") diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlGroupRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlGroupRepositoryIntegrationSpec.scala index b658c741a..c207c880d 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlGroupRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlGroupRepositoryIntegrationSpec.scala @@ -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 { "retrieve a group" in { repo.getGroupByName(groups.head.name).unsafeRunSync() shouldBe Some(groups.head) From 75feb72db43dd779499205e7b28fa72cb10a8d98 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 21 Sep 2022 17:40:59 +0530 Subject: [PATCH 08/30] Refactor functionality and tests --- .../api/src/main/resources/application.conf | 40 +- modules/api/src/main/resources/reference.conf | 22 +- .../src/main/scala/vinyldns/api/Boot.scala | 3 +- .../api/config/DottedHostsConfig.scala | 19 +- .../api/domain/batch/BatchChangeService.scala | 95 +---- .../domain/batch/BatchChangeValidations.scala | 83 ++-- .../api/domain/record/RecordSetService.scala | 114 +++++- .../domain/record/RecordSetValidations.scala | 42 +- .../vinyldns/api/VinylDNSTestHelpers.scala | 4 +- .../domain/batch/BatchChangeServiceSpec.scala | 28 +- .../batch/BatchChangeValidationsSpec.scala | 368 ++++-------------- .../domain/record/RecordSetServiceSpec.scala | 203 +++++++--- .../record/RecordSetValidationsSpec.scala | 95 ++--- .../scala/vinyldns/core/TestZoneData.scala | 2 +- 14 files changed, 478 insertions(+), 640 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index c0f51b0ce..073b7065a 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,25 +165,27 @@ vinyldns { "ns1.parent.com4." ] - # approved zones, individual users, users in groups and record types that are allowed for dotted hosts - dotted-hosts = { - zone-list = [ - "dummy", # for local testing - "*ent.com" # for local testing with wildcard zones (allows parent.com and child.parent.com zones) - ] - allowed-user-list = [ - "testuser", - "professor" # for local testing (username) - ] - allowed-group-list = [ - "test-group", - "duGroup" # for local testing (group name) - ] - allowed-record-type = [ - "A", - "CNAME" # for local testing - ] - } +# approved zones, individual users, users in groups and record types that are allowed for dotted hosts +dotted-hosts = { + # for local testing + allowed-settings = [ + { + type = "auth-configs" + zone = "dummy." + allowed-user-list = ["testuser"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["AAAA"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com." + allowed-user-list = ["professor", "testuser"] + allowed-group-list = ["testing-group"] + allowed-record-type = ["A", "CNAME"] + } + ] +} # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production crypto { diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index a4d8b19d7..da75134ca 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -92,18 +92,16 @@ vinyldns { # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { - zone-list = [ - "parent.com*" # for local testing with wildcard zones (allows parent.com and child.parent.com zones) - ] - allowed-user-list = [ - "ok" # for local testing (username) - ] - allowed-group-list = [ - "dummy-group*" # for local testing (group name) - ] - allowed-record-type = [ - "CNAME" # for local testing - ] + allowed-settings = [ + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com*." + allowed-user-list = ["ok"] + allowed-group-list = ["dummy-group*"] + allowed-record-type = ["CNAME"] + } + ] } # color should be green or blue, used in order to do blue/green deployment diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index aa7cffb33..ae4077f49 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -184,8 +184,7 @@ object Boot extends App { notifiers, vinyldnsConfig.scheduledChangesConfig.enabled, vinyldnsConfig.batchChangeConfig.v6DiscoveryNibbleBoundaries, - vinyldnsConfig.serverConfig.defaultTtl, - vinyldnsConfig.dottedHostsConfig + vinyldnsConfig.serverConfig.defaultTtl ) val collectorRegistry = CollectorRegistry.defaultRegistry val vinyldnsService = new VinylDNSService( diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index 2a5051976..183b1b4e0 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -17,17 +17,16 @@ package vinyldns.api.config import pureconfig.ConfigReader +import pureconfig.generic.auto._ + +sealed trait AuthMethod +final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) extends AuthMethod +final case class DottedHostsConfig(authMethods: List[AuthMethod]) -final case class DottedHostsConfig(zoneList: List[String], allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) object DottedHostsConfig { implicit val configReader: ConfigReader[DottedHostsConfig] = - ConfigReader.forProduct4[DottedHostsConfig, List[String], List[String], List[String], List[String]]( - "zone-list", - "allowed-user-list", - "allowed-group-list", - "allowed-record-type" - ){ - case (zoneList, allowedUserList, allowedGroupList, allowedRecordType) => - DottedHostsConfig(zoneList, allowedUserList, allowedGroupList, allowedRecordType) - } + ConfigReader.forProduct1[DottedHostsConfig, List[AuthMethod]]( + "allowed-settings", + )(authMethods => + DottedHostsConfig(authMethods)) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index f10094d3a..ae2ff56c4 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -27,7 +27,6 @@ import vinyldns.api.domain.auth.AuthPrincipalProvider import vinyldns.api.domain.batch.BatchChangeInterfaces._ import vinyldns.api.domain.batch.BatchTransformations._ import vinyldns.api.backend.dns.DnsConversions._ -import vinyldns.api.config.DottedHostsConfig import vinyldns.api.repository.ApiDataAccessor import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.batch.BatchChangeApprovalStatus.BatchChangeApprovalStatus @@ -36,7 +35,7 @@ import vinyldns.core.domain.batch.BatchChangeApprovalStatus._ import vinyldns.core.domain.{CnameAtZoneApexError, SingleChangeError, UserIsNotAuthorizedError, ZoneDiscoveryError} import vinyldns.core.domain.membership.{Group, GroupRepository, ListUsersResults, User, UserRepository} import vinyldns.core.domain.record.RecordType._ -import vinyldns.core.domain.record.{RecordSet, RecordSetRepository} +import vinyldns.core.domain.record.RecordSetRepository import vinyldns.core.domain.zone.ZoneRepository import vinyldns.core.notifier.{AllNotifiers, Notification} @@ -50,8 +49,7 @@ object BatchChangeService { notifiers: AllNotifiers, scheduledChangesEnabled: Boolean, v6DiscoveryNibbleBoundaries: V6DiscoveryNibbleBoundaries, - defaultTtl: Long, - dottedHostsConfig: DottedHostsConfig + defaultTtl: Long ): BatchChangeService = new BatchChangeService( dataAccessor.zoneRepository, @@ -66,8 +64,7 @@ object BatchChangeService { notifiers, scheduledChangesEnabled, v6DiscoveryNibbleBoundaries, - defaultTtl, - dottedHostsConfig + defaultTtl ) } @@ -84,8 +81,7 @@ class BatchChangeService( notifiers: AllNotifiers, scheduledChangesEnabled: Boolean, v6zoneNibbleBoundaries: V6DiscoveryNibbleBoundaries, - defaultTtl: Long, - dottedHostsConfig: DottedHostsConfig + defaultTtl: Long ) extends BatchChangeServiceAlgebra { import batchChangeValidations._ @@ -126,97 +122,16 @@ class BatchChangeService( recordSets <- getExistingRecordSets(changesWithZones, zoneMap).toBatchResult withTtl = doTtlMapping(changesWithZones, recordSets) groupedChanges = ChangeForValidationMap(withTtl, recordSets) - allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toBatchResult - isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) - isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toBatchResult - isAllowedUser = isInAllowedUsers || isUserInAllowedGroups - zoneOrRecordDoesNotAlreadyExist <- zoneOrRecordDoesNotExist(groupedChanges).toBatchResult validatedSingleChanges = validateChangesWithContext( groupedChanges, auth, isApproved, - batchChangeInput.ownerGroupId, - zoneOrRecordDoesNotAlreadyExist, - allowedZoneList, - isAllowedUser, - dottedHostsConfig + batchChangeInput.ownerGroupId ) errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges) validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges) } yield BatchValidationFlowOutput(validatedSingleChangesWithGroups, zoneMap, groupedChanges) - // For dotted hosts. Check if a zone or record that may conflict with dotted host exist or not - def zoneOrRecordDoesNotExist(groupedChanges: ChangeForValidationMap): IO[Boolean] = { - val groupedChangesMap = groupedChanges.changes.map(x => x.toOption).filter(x => x.isDefined) - - val newRecordFqdn = if(groupedChangesMap.nonEmpty){ - val inputChange = groupedChangesMap.map(x => x.get) - // Use fqdn for searching through `recordset` and `zone` mysql table to see if it already exist - inputChange.map(_.recordName).head + "." + inputChange.map(_.zone.name).head - } else { - "" - } - - for { - zone <- zoneRepository.getZoneByName(newRecordFqdn) - record <- recordSetRepository.getRecordSetsByFQDNs(Set(newRecordFqdn)) - isZoneAlreadyExist = zone.isDefined - isRecordAlreadyExist = if(groupedChangesMap.nonEmpty) doesRecordWithSameTypeExist(record, groupedChangesMap.map(x => x.get).map(_.inputChange)) else false - doesNotExist = if(isZoneAlreadyExist || 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: List[ChangeInput]): Boolean = { - if(oldRecord.nonEmpty) { - val typeExists = oldRecord.map(x => x.typ == newRecord.map(_.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 checkIfInAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { - if(users.contains(auth.signedInUser.userName)){ - true - } - else { - false - } - } - - // Check if user is allowed to create dotted hosts using the groups present in dotted hosts config - def checkIfInAllowedGroups(groups: List[String], auth: AuthPrincipal): IO[Boolean] = { - 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 getGroupIdsFromUnauthorizedErrors( changes: ValidatedBatch[ChangeForValidation] ): BatchResult[Set[Group]] = { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index fd1eac402..233ac590c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -19,7 +19,7 @@ package vinyldns.api.domain.batch import java.net.InetAddress import cats.data._ import cats.implicits._ -import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{BatchChangeConfig, HighValueDomainConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.DomainValidations._ import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.auth.AuthPrincipal @@ -45,14 +45,10 @@ trait BatchChangeValidationsAlgebra { ): ValidatedBatch[ChangeInput] def validateChangesWithContext( - groupedChanges: ChangeForValidationMap, - auth: AuthPrincipal, - isApproved: Boolean, - batchOwnerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean, - dottedHostsConfig: DottedHostsConfig + groupedChanges: ChangeForValidationMap, + auth: AuthPrincipal, + isApproved: Boolean, + batchOwnerGroupId: Option[String] ): ValidatedBatch[ChangeForValidation] def canGetBatchChange( @@ -270,22 +266,18 @@ class BatchChangeValidations( /* context validations */ def validateChangesWithContext( - groupedChanges: ChangeForValidationMap, - auth: AuthPrincipal, - isApproved: Boolean, - batchOwnerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean, - dottedHostsConfig: DottedHostsConfig + groupedChanges: ChangeForValidationMap, + auth: AuthPrincipal, + isApproved: Boolean, + batchOwnerGroupId: Option[String] ): 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 { case add: AddChangeForValidation - if groupedChanges - .getLogicalChangeType(add.recordKey) - .contains(LogicalChangeType.Add) => - validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isUserAllowed, dottedHostsConfig) + if groupedChanges + .getLogicalChangeType(add.recordKey) + .contains(LogicalChangeType.Add) => + validateAddWithContext(add, groupedChanges, auth, isApproved, batchOwnerGroupId) case addUpdate: AddChangeForValidation => validateAddUpdateWithContext(addUpdate, groupedChanges, auth, isApproved, batchOwnerGroupId) // These cases MUST be below adds because: @@ -300,30 +292,11 @@ class BatchChangeValidations( validateDeleteUpdateWithContext(deleteUpdate, groupedChanges, auth, isApproved) } - // Check if the new record set has dots and if so whether they are allowed or not - def newRecordSetDottedCheck(change: AddChangeForValidation, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isUserAllowed: Boolean, dottedHostsConfig: DottedHostsConfig): SingleValidation[Unit] = { - - // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostZoneConfig.contains(change.zone.name) - - val isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(change.inputChange.typ.toString) - - // Check if record set contains dot and if it is in zone which is allowed to have dotted records from dotted hosts config - if((change.recordName.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && isUserAllowed && isRecordTypeAllowed && change.recordName != change.zone.name) { - // If the user is not authorized or if there is a zone/record already present which conflicts with the new dotted record, throw an error - if (!zoneOrRecordDoesNotAlreadyExist || change.recordName == change.zone.name || !isUserAllowed) - DottedHostError(change.recordName, change.zone.name).invalidNel - else - ().validNel - } - else { - // If the recordset contains dot but is not in the allowed zones to create dotted records, throw an error - if (change.recordName != change.zone.name && change.recordName.contains(".")) - ZoneDiscoveryError(change.inputChange.inputName).invalidNel - else - ().validNel - } - } + def newRecordSetIsNotDotted(change: AddChangeForValidation): SingleValidation[Unit] = + if (change.recordName != change.zone.name && change.recordName.contains(".")) + ZoneDiscoveryError(change.inputChange.inputName).invalidNel + else + ().validNel def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean = existingRecordSetData.exists { rd => @@ -429,22 +402,18 @@ class BatchChangeValidations( } def validateAddWithContext( - change: AddChangeForValidation, - groupedChanges: ChangeForValidationMap, - auth: AuthPrincipal, - isApproved: Boolean, - ownerGroupId: Option[String], - zoneOrRecordDoesNotAlreadyExist: Boolean, - dottedHostZoneConfig: Set[String], - isUserAllowed: Boolean, - dottedHostsConfig: DottedHostsConfig + change: AddChangeForValidation, + groupedChanges: ChangeForValidationMap, + auth: AuthPrincipal, + isApproved: Boolean, + ownerGroupId: Option[String] ): SingleValidation[ChangeForValidation] = { val typedValidations = change.inputChange.typ match { case A | AAAA | MX => - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed, dottedHostsConfig) + newRecordSetIsNotDotted(change) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| - newRecordSetDottedCheck(change, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig: Set[String], isUserAllowed, dottedHostsConfig) + newRecordSetIsNotDotted(change) case TXT | PTR => ().validNel case other => diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index ef118e604..210d26937 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -28,7 +28,7 @@ import vinyldns.core.queue.MessageQueue import cats.data._ import cats.effect.IO import org.xbill.DNS.ReverseMap -import vinyldns.api.config.{DottedHostsConfig, HighValueDomainConfig} +import vinyldns.api.config.{AuthConfigs, DottedHostsConfig, HighValueDomainConfig} import vinyldns.api.domain.DomainValidations.{validateIpv4Address, validateIpv6Address} import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.record.NameSort.NameSort @@ -110,20 +110,23 @@ class RecordSetService( ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] - isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) - isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toResult[Boolean] + authZones = dottedHostsConfig.authMethods.map { + case y:AuthConfigs => y.zone + } + allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]] + isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth) + isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean] isAllowedUser = isInAllowedUsers || isUserInAllowedGroups - isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) + isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed - zoneOrRecordDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] + recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, zone, None, approvedNameServers, - zoneOrRecordDoesNotAlreadyExist, + recordFqdnDoesNotAlreadyExist, allowedZoneList, isRecordTypeAndUserAllowed ).toResult @@ -156,20 +159,23 @@ class RecordSetService( validateRecordLookupAgainstDnsBackend ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - allowedZoneList <- getAllowedZones(dottedHostsConfig.zoneList).toResult[Set[String]] - isInAllowedUsers = checkIfInAllowedUsers(dottedHostsConfig.allowedUserList, auth) - isUserInAllowedGroups <- checkIfInAllowedGroups(dottedHostsConfig.allowedGroupList, auth).toResult[Boolean] + authZones = dottedHostsConfig.authMethods.map { + case y:AuthConfigs => y.zone + } + allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]] + isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth) + isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean] isAllowedUser = isInAllowedUsers || isUserInAllowedGroups - isRecordTypeAllowed = dottedHostsConfig.allowedRecordType.contains(rsForValidations.typ.toString) + isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed - zoneOrRecordDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] + recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, existingRecordsWithName, zone, Some(existing), approvedNameServers, - zoneOrRecordDoesNotAlreadyExist, + recordFqdnDoesNotAlreadyExist, allowedZoneList, isRecordTypeAndUserAllowed, ).toResult @@ -223,7 +229,7 @@ class RecordSetService( } else { // Wildcard zones needs to be passed to a separate method - val wildcardZones = zones.filter(_.contains("*")).map(_.replace("*", "")) + 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{ @@ -235,17 +241,87 @@ class RecordSetService( } // Check if user is allowed to create dotted hosts using the users present in dotted hosts config - def checkIfInAllowedUsers(users: List[String], auth: AuthPrincipal): Boolean = { - if(users.contains(auth.signedInUser.userName)){ - true + def checkIfInAllowedUsers(zone: Zone, config: DottedHostsConfig, auth: AuthPrincipal): Boolean = { + val configZones = config.authMethods.map{ + case x: AuthConfigs => x.zone } - else { + 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(isContainWildcardZone || isContainNormalZone){ + val users = config.authMethods.flatMap { + case x: AuthConfigs => if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if(zoneName.matches(wildcardZone)) x.allowedUserList else List.empty + } else { + if (x.zone == zoneName) x.allowedUserList 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 users present in dotted hosts config + def checkIfInAllowedRecordType(zone: Zone, config: DottedHostsConfig, rs: RecordSet): Boolean = { + val configZones = config.authMethods.map{ + case x: AuthConfigs => 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(isContainWildcardZone || isContainNormalZone){ + val rType = config.authMethods.flatMap { + case x: AuthConfigs => if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if(zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty + } else { + if (x.zone == zoneName) x.allowedRecordType 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(groups: List[String], auth: AuthPrincipal): IO[Boolean] = { + def checkIfInAllowedGroups(zone: Zone, config: DottedHostsConfig, auth: AuthPrincipal): IO[Boolean] = { + val configZones = config.authMethods.map{ + case x: AuthConfigs => 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(isContainWildcardZone || isContainNormalZone){ + config.authMethods.flatMap { + case x: AuthConfigs => if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if(zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty + } else { + if (x.zone == zoneName) x.allowedGroupList else List.empty + } + } + } + else { + List.empty + } for{ groupsInConfig <- groupRepository.getGroupsByName(groups.toSet) members = groupsInConfig.flatMap(x => x.memberIds) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 1fe942aa0..1d7c3a427 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -95,17 +95,17 @@ object RecordSetValidations { newRecordSet: RecordSet, zone: Zone, existingRecordSet: Option[RecordSet] = None, - zoneOrRecordDoesNotAlreadyExist: Boolean, + recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { + val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name // Check if the zone of the record set is present in dotted hosts config list - val isDomainAllowed = dottedHostZoneConfig.contains(zone.name) - + 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((newRecordSet.name.contains(".") || !zoneOrRecordDoesNotAlreadyExist) && isDomainAllowed && isRecordTypeAndUserAllowed && newRecordSet.name != zone.name) { - isDotted(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, isRecordTypeAndUserAllowed) + if((newRecordSet.name.contains(".") || !recordFqdnDoesNotExist) && isDomainAllowed && isRecordTypeAndUserAllowed && newRecordSet.name != zone.name) { + isDotted(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, isRecordTypeAndUserAllowed) } else { isNotDotted(newRecordSet, zone, existingRecordSet) @@ -117,7 +117,7 @@ object RecordSetValidations { newRecordSet: RecordSet, zone: Zone, existingRecordSet: Option[RecordSet] = None, - zoneOrRecordDoesNotAlreadyExist: Boolean, + recordFqdnDoesNotExist: Boolean, isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = ensuring( @@ -126,7 +126,7 @@ object RecordSetValidations { s"Please check if user has permission or if there's a zone/record that already exist and make the change there." ) )( - (newRecordSet.name != zone.name || existingRecordSet.exists(_.name == newRecordSet.name)) && zoneOrRecordDoesNotAlreadyExist && isRecordTypeAndUserAllowed + (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 @@ -151,18 +151,18 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet], approvedNameServers: List[Regex], - zoneOrRecordDoesNotAlreadyExist: Boolean = true, + recordFqdnDoesNotExist: Boolean = true, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = newRecordSet.typ match { - case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case SOA => soaValidations(newRecordSet, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case SOA => soaValidations(newRecordSet, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) case PTR => ptrValidations(newRecordSet, zone) case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check - case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case _ => checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case _ => checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) } def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = @@ -184,7 +184,7 @@ object RecordSetValidations { existingRecordsWithName: List[RecordSet], zone: Zone, existingRecordSet: Option[RecordSet] = None, - zoneOrRecordDoesNotAlreadyExist: Boolean, + recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { @@ -219,7 +219,7 @@ object RecordSetValidations { ) _ <- noRecordWithName _ <- RDataWithConsecutiveDots - _ <- checkForDot(newRecordSet, zone, existingRecordSet, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + _ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) } yield () } @@ -228,7 +228,7 @@ object RecordSetValidations { newRecordSet: RecordSet, existingRecordsWithName: List[RecordSet], zone: Zone, - zoneOrRecordDoesNotAlreadyExist: Boolean, + recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { @@ -243,7 +243,7 @@ object RecordSetValidations { } for { - _ <- checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + _ <- checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) _ <- isNotOrigin( newRecordSet, zone, @@ -258,12 +258,12 @@ object RecordSetValidations { zone: Zone, oldRecordSet: Option[RecordSet], approvedNameServers: List[Regex], - zoneOrRecordDoesNotAlreadyExist: Boolean, + recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = { // TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically - val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight + val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight for { _ <- isNotDottedHost @@ -285,9 +285,9 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone, zoneOrRecordDoesNotAlreadyExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean): Either[Throwable, Unit] = + def soaValidations(newRecordSet: RecordSet, zone: Zone, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean): Either[Throwable, Unit] = // TODO kept consistency with old validation. in theory if SOA always == zone name, no special case is needed here - if (!zone.isReverse) checkForDot(newRecordSet, zone, None, zoneOrRecordDoesNotAlreadyExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight + if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = // TODO we don't check for PTR as dotted...not sure why diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index 70ec98f27..24f0ae8c4 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -18,7 +18,7 @@ package vinyldns.api import com.comcast.ip4s.IpAddress import org.joda.time.DateTime -import vinyldns.api.config.{BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{AuthConfigs, BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.batch.V6DiscoveryNibbleBoundaries import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ @@ -40,7 +40,7 @@ trait VinylDNSTestHelpers { val approvedNameServers: List[Regex] = List(new Regex("some.test.ns.")) - val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List("dotted.xyz"), List("super"), List("dummy"), List("CNAME")) + val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(AuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME")), AuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME")), AuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME")))) val defaultTtl: Long = 7200 diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala index d80caee54..ffc29190e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala @@ -55,7 +55,7 @@ import vinyldns.api.domain.access.AccessValidations import scala.concurrent.ExecutionContext class BatchChangeServiceSpec - extends AnyWordSpec + extends AnyWordSpec with Matchers with MockitoSugar with CatsHelpers @@ -418,8 +418,7 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) private val underTestManualEnabled = new BatchChangeService( @@ -435,8 +434,7 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) private val underTestScheduledEnabled = new BatchChangeService( @@ -452,8 +450,7 @@ class BatchChangeServiceSpec mockNotifiers, true, defaultv6Discovery, - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) "applyBatchChange" should { @@ -479,8 +476,7 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 17), - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) val ptr = AddChangeInput( "2001:0000:0000:0001:0000:ff00:0042:8329", @@ -511,8 +507,7 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 16), - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) val ptr = AddChangeInput( "2001:0000:0000:0001:0000:ff00:0042:8329", @@ -1182,8 +1177,7 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329" @@ -1224,8 +1218,7 @@ class BatchChangeServiceSpec mockNotifiers, false, new V6DiscoveryNibbleBoundaries(16, 16), - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329" @@ -1251,8 +1244,7 @@ class BatchChangeServiceSpec mockNotifiers, false, defaultv6Discovery, - 7200L, - VinylDNSTestHelpers.dottedHostsConfig + 7200L ) val ip1 = "::1" @@ -2566,4 +2558,4 @@ class BatchChangeServiceSpec ) } } -} +} \ No newline at end of file diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 43bad4d9f..72d48401c 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -41,7 +41,7 @@ import vinyldns.core.domain.zone.{ACLRule, AccessLevel, Zone, ZoneStatus} import scala.util.Random class BatchChangeValidationsSpec - extends AnyPropSpec + extends AnyPropSpec with Matchers with ScalaCheckDrivenPropertyChecks with EitherMatchers @@ -192,9 +192,9 @@ class BatchChangeValidationsSpec ) private def makeAddUpdateRecord( - recordName: String, - aData: AData = AData("1.2.3.4") - ): AddChangeForValidation = + recordName: String, + aData: AData = AData("1.2.3.4") + ): AddChangeForValidation = AddChangeForValidation( okZone, s"$recordName", @@ -203,9 +203,9 @@ class BatchChangeValidationsSpec ) private def makeDeleteUpdateDeleteRRSet( - recordName: String, - recordData: Option[RecordData] = None - ): DeleteRRSetChangeForValidation = + recordName: String, + recordData: Option[RecordData] = None + ): DeleteRRSetChangeForValidation = DeleteRRSetChangeForValidation( okZone, s"$recordName", @@ -666,7 +666,7 @@ class BatchChangeValidationsSpec } 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 change = AddChangeInput("test.comcast.com.", RecordType.A, ttl, AData(invalidIpv4)) val result = validateAddChangeInput(change, false) @@ -819,11 +819,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -888,11 +884,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.foreach(_ shouldBe valid) @@ -932,11 +924,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.foreach(_ shouldBe valid) @@ -963,11 +951,7 @@ class BatchChangeValidationsSpec ), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -998,11 +982,7 @@ class BatchChangeValidationsSpec ), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1042,11 +1022,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1069,7 +1045,7 @@ class BatchChangeValidationsSpec property( """validateChangesWithContext: should succeed for update in shared zone if user belongs to record - | owner group""".stripMargin + | owner group""".stripMargin ) { val existingRecord = sharedZoneRecord.copy( @@ -1096,11 +1072,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1108,7 +1080,7 @@ class BatchChangeValidationsSpec } 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 existingCname2 = existingCname.copy(name = "deleteRecord", records = List(CNAMEData(Fqdn("cname.data.")))) @@ -1138,11 +1110,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.foreach(_ shouldBe valid) @@ -1165,11 +1133,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(newRecordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1186,11 +1150,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1206,11 +1166,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid } @@ -1229,11 +1185,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(existingRecordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1265,11 +1217,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1277,7 +1225,7 @@ class BatchChangeValidationsSpec } property("""validateChangesWithContext: should fail with CnameIsNotUniqueError - |if CNAME record name already exists""".stripMargin) { + |if CNAME record name already exists""".stripMargin) { val addCname = AddChangeForValidation( validZone, "existingCname", @@ -1290,11 +1238,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel), ExistingRecordSets(newRecordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1303,7 +1247,7 @@ class BatchChangeValidationsSpec } 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( validIp4ReverseZone, "30", @@ -1323,11 +1267,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1335,7 +1275,7 @@ class BatchChangeValidationsSpec } 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( validZone, "0.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", @@ -1352,11 +1292,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel), ExistingRecordSets(List(existingRecordPTR))), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1365,7 +1301,7 @@ class BatchChangeValidationsSpec } 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( okZone, "test", @@ -1391,11 +1327,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1404,7 +1336,7 @@ class BatchChangeValidationsSpec } 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( okZone, "test", @@ -1430,11 +1362,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1448,7 +1376,7 @@ class BatchChangeValidationsSpec } 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( okZone, "test", @@ -1474,11 +1402,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1494,7 +1418,7 @@ class BatchChangeValidationsSpec } 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( okZone, "test", @@ -1520,18 +1444,14 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } property("""validateChangesWithContext: should succeed for AddChangeForValidation - |if user has group admin access""".stripMargin) { + |if user has group admin access""".stripMargin) { val addA = AddChangeForValidation( validZone, "valid", @@ -1543,11 +1463,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1566,11 +1482,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), AuthPrincipal(superUser, Seq.empty), false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1597,11 +1509,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addA.validNel), ExistingRecordSets(recordSetList)), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1617,11 +1525,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(input.validNel), ExistingRecordSets(recordSetList)), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1636,7 +1540,7 @@ class BatchChangeValidationsSpec } 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( validZone, "existing", @@ -1653,11 +1557,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addCname.validNel, addPtr.validNel), ExistingRecordSets(List())), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1682,11 +1582,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1707,11 +1603,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1723,7 +1615,7 @@ class BatchChangeValidationsSpec } property("""validateChangesWithContext: should succeed for DeleteChangeForValidation - |if record set status is Active""".stripMargin) { + |if record set status is Active""".stripMargin) { val deleteA = DeleteRRSetChangeForValidation( validZone, "Active-record-status", @@ -1741,18 +1633,14 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid } property("""validateChangesWithContext: should succeed for DeleteChangeForValidation - |if user has group admin access"""".stripMargin) { + |if user has group admin access"""".stripMargin) { val deleteA = DeleteRRSetChangeForValidation( validZone, @@ -1767,18 +1655,14 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid } 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 = DeleteRRSetChangeForValidation( validZone, @@ -1793,11 +1677,7 @@ class BatchChangeValidationsSpec ), AuthPrincipal(superUser, Seq.empty), false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1828,11 +1708,7 @@ class BatchChangeValidationsSpec ), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1855,11 +1731,7 @@ class BatchChangeValidationsSpec ), notAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError]( @@ -1873,7 +1745,7 @@ class BatchChangeValidationsSpec } 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( okZone, "test", @@ -1927,11 +1799,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -1980,11 +1848,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } @@ -2027,11 +1891,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } @@ -2061,11 +1921,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } @@ -2103,11 +1959,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2146,11 +1998,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } @@ -2183,11 +2031,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) } @@ -2225,11 +2069,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result.map(_ shouldBe valid) @@ -2343,11 +2183,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addMX.validNel), ExistingRecordSets(List(existingMX))), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError](RecordAlreadyExists("name-conflict.")) } @@ -2370,11 +2206,7 @@ class BatchChangeValidationsSpec ChangeForValidationMap(List(addMx.validNel, addMx2.validNel), ExistingRecordSets(List())), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid } @@ -2405,11 +2237,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid } @@ -2438,11 +2266,7 @@ class BatchChangeValidationsSpec ), AuthPrincipal(okUser, Seq(abcGroup.id, okGroup.id)), false, - Some("some-owner-group-id"), - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + Some("some-owner-group-id") ) result.foreach(_ shouldBe valid) @@ -2472,11 +2296,7 @@ class BatchChangeValidationsSpec ), AuthPrincipal(okUser, Seq(abcGroup.id, okGroup.id)), false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2505,11 +2325,7 @@ class BatchChangeValidationsSpec ), dummyAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should @@ -2533,11 +2349,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2551,11 +2363,7 @@ class BatchChangeValidationsSpec ), sharedAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2589,11 +2397,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - Some(okGroup.id), - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + Some(okGroup.id) ) result(0) shouldBe valid @@ -2638,11 +2442,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - Some(okGroup.id), - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + Some(okGroup.id) ) result(0) shouldBe valid @@ -2656,7 +2456,7 @@ class BatchChangeValidationsSpec property( """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( okZone, @@ -2701,11 +2501,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) should haveInvalid[DomainValidationError](ZoneDiscoveryError("dotted.a.ok.")) @@ -2759,11 +2555,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2865,11 +2657,7 @@ class BatchChangeValidationsSpec ), okAuth, false, - None, - true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, - false, - VinylDNSTestHelpers.dottedHostsConfig + None ) result(0) shouldBe valid @@ -2878,4 +2666,4 @@ class BatchChangeValidationsSpec result(3) shouldBe valid result(4) shouldBe valid } -} +} \ No newline at end of file diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 63ee4ccf0..7174a2c72 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -24,6 +24,7 @@ import org.scalatestplus.mockito.MockitoSugar import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.BeforeAndAfterEach +import vinyldns.api.config.{AuthConfigs, DottedHostsConfig} import vinyldns.api.{ResultHelpers, VinylDNSTestHelpers} import vinyldns.api.domain.access.AccessValidations import vinyldns.api.domain.record.RecordSetHelpers._ @@ -107,6 +108,36 @@ class RecordSetServiceSpec true ) + def getDottedHostsConfigGroupsAllowed(zone: Zone, config: DottedHostsConfig): List[String] = { + val configZones = config.authMethods.map { + case x: AuthConfigs => 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.authMethods.flatMap { + case x: AuthConfigs => if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z.]*") + if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.allowedGroupList else List.empty + } else { + if (x.zone == zoneName) x.allowedGroupList else List.empty + } + } + } + else { + List.empty + } + groups + } + + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { + case y:AuthConfigs => y.zone + } + + val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(okZone, VinylDNSTestHelpers.dottedHostsConfig) + "addRecordSet" should { "return the recordSet change as the result" in { val record = aaaa.copy(zoneId = okZone.id) @@ -119,7 +150,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -134,7 +165,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -189,7 +220,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -204,7 +235,7 @@ class RecordSetServiceSpec .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -224,7 +255,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -239,7 +270,7 @@ class RecordSetServiceSpec .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -270,7 +301,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -285,7 +316,7 @@ class RecordSetServiceSpec .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -309,7 +340,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -324,7 +355,7 @@ class RecordSetServiceSpec .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -367,7 +398,7 @@ class RecordSetServiceSpec .getGroup(okGroup.id) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -382,7 +413,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -441,7 +472,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -456,7 +487,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -478,6 +509,12 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { + case y:AuthConfigs => 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) @@ -487,7 +524,7 @@ class RecordSetServiceSpec .getRecordSetsByName(dottedZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -500,9 +537,55 @@ class RecordSetServiceSpec 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.authMethods.map { + case y:AuthConfigs => y.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))) + .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(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None))) .when(mockUserRepo) .getUsers(xyzGroup.memberIds, None, None) @@ -526,7 +609,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -539,12 +622,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) - doReturn(IO.pure(Set(xyzGroup))) + doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) - doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None))) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) + doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) - .getUsers(xyzGroup.memberIds, None, None) + .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) @@ -552,45 +635,57 @@ class RecordSetServiceSpec } "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 = dottedZone.id, status = RecordSetStatus.Active) + cname.copy(name = "new.name", zoneId = abcZone.id, status = RecordSetStatus.Active) - doReturn(IO.pure(Some(dottedZone))).when(mockZoneRepo).getZone(dottedZone.id) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { + case y:AuthConfigs => y.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(dottedZone.id, record.name, record.typ) + .getRecordSets(abcZone.id, record.name, record.typ) doReturn(IO.pure(List())) .when(mockRecordRepo) - .getRecordSetsByName(dottedZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + .getRecordSetsByName(abcZone.id, record.name) + doReturn(IO.pure(Set(abcZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) doReturn(IO.pure(None)) .when(mockZoneRepo) - .getZoneByName(record.name + "." + dottedZone.name) + .getZoneByName(record.name + "." + abcZone.name) doReturn(IO.pure(List())) .when(mockRecordRepo) - .getRecordSetsByFQDNs(Set(record.name + "." + dottedZone.name)) + .getRecordSetsByFQDNs(Set(record.name + "." + abcZone.name)) doReturn(IO.pure(Set())) .when(mockZoneRepo) - .getZonesByFilters(record.name.split('.').map(x => x + "." + dottedZone.name).toSet) - doReturn(IO.pure(Set(xyzGroup))) + .getZonesByFilters(record.name.split('.').map(x => x + "." + abcZone.name).toSet) + doReturn(IO.pure(Set(dummyGroup))) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) - doReturn(IO.pure(ListUsersResults(Seq(), None))) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) + doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None))) .when(mockUserRepo) - .getUsers(xyzGroup.memberIds, None, None) + .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, xyzAuth).value) + 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.authMethods.map { + case y:AuthConfigs => 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) @@ -600,7 +695,7 @@ class RecordSetServiceSpec .getRecordSetsByName(dottedZone.id, record.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -613,12 +708,12 @@ class RecordSetServiceSpec doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(record.name.split('.').map(x => x + "." + dottedZone.name).toSet) - doReturn(IO.pure(Set(xyzGroup))) + doReturn(IO.pure(Set(dummyGroup))) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) - doReturn(IO.pure(ListUsersResults(Seq(xyzUser), None))) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) + doReturn(IO.pure(ListUsersResults(listOfDummyUsers.toSeq, None))) .when(mockUserRepo) - .getUsers(xyzGroup.memberIds, None, None) + .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) @@ -641,7 +736,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -656,7 +751,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -698,7 +793,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -713,7 +808,7 @@ class RecordSetServiceSpec .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -758,7 +853,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -773,7 +868,7 @@ class RecordSetServiceSpec .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -801,7 +896,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -816,7 +911,7 @@ class RecordSetServiceSpec .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -844,7 +939,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -859,7 +954,7 @@ class RecordSetServiceSpec .getZonesByFilters(newRecord.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -1000,7 +1095,7 @@ class RecordSetServiceSpec .getGroup(oneUserDummyGroup.id) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -1015,7 +1110,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) @@ -1050,7 +1145,7 @@ class RecordSetServiceSpec .getRecordSetsByName(zone.id, newRecord.name) doReturn(IO.pure(Set(dottedZone))) .when(mockZoneRepo) - .getZonesByNames(VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet) + .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) .when(mockZoneRepo) .getZonesByFilters(Set.empty) @@ -1065,7 +1160,7 @@ class RecordSetServiceSpec .getZonesByFilters(Set.empty) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(VinylDNSTestHelpers.dottedHostsConfig.allowedGroupList.toSet) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index 57625b9c1..f39600191 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -25,6 +25,7 @@ import vinyldns.core.domain.record.RecordType._ import vinyldns.api.domain.zone.{InvalidGroupError, InvalidRequest, PendingUpdateError, RecordSetAlreadyExists, RecordSetValidation} import vinyldns.api.ResultHelpers import vinyldns.api.VinylDNSTestHelpers +import vinyldns.api.config.AuthConfigs import vinyldns.core.TestRecordSetData._ import vinyldns.core.TestZoneData._ import vinyldns.core.TestMembershipData._ @@ -44,6 +45,10 @@ class RecordSetValidationsSpec import RecordSetValidations._ + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { + case y:AuthConfigs => y.zone + } + "RecordSetValidations" should { "validRecordTypes" should { "return invalid request when adding a PTR record to a forward zone" in { @@ -219,34 +224,34 @@ class RecordSetValidationsSpec val dottedARecord = rsOk.copy(name = "this.is.a.failure.") "return a failure for any new record with dotted hosts in forward zones" in { leftValue( - typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) + typeSpecificValidations(dottedARecord, List(), okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (CNAME)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) + typeSpecificValidations(dottedARecord.copy(typ = CNAME), List(), okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) ) shouldBe an[InvalidRequest] } "return a failure for any new record with dotted hosts in forward zones (NS)" in { leftValue( - typeSpecificValidations(dottedARecord.copy(typ = NS), List(), okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) + 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 { - val record = typeSpecificValidations(dottedARecord.copy(zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + val record = typeSpecificValidations(dottedARecord.copy(zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) record should be(right) } "return a failure for any new record with dotted hosts in forward zones (CNAME) if it satisfies dotted hosts configs" in { - val record = typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + val record = typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) record should be(right) } "return a failure for any new record with dotted hosts in forward zones (NS) if it satisfies dotted hosts configs" in { - val record = typeSpecificValidations(dottedARecord.copy(typ = NS, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + val record = typeSpecificValidations(dottedARecord.copy(typ = NS, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) record should be(right) } @@ -258,7 +263,7 @@ class RecordSetValidationsSpec Some(dottedARecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + dottedHostsConfigZonesAllowed.toSet, false ) should be(right) } @@ -272,7 +277,7 @@ class RecordSetValidationsSpec Some(dottedCNAMERecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + dottedHostsConfigZonesAllowed.toSet, false ) should be(right) } @@ -287,7 +292,7 @@ class RecordSetValidationsSpec Some(dottedNSRecord.copy(ttl = 300)), Nil, true, - VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, + dottedHostsConfigZonesAllowed.toSet, false ) ) shouldBe an[InvalidRequest] @@ -299,35 +304,35 @@ class RecordSetValidationsSpec val test = srv.copy(name = "_sip._tcp.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { val test = srv.copy(name = "_sip._tcp") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { val test = srv.copy(name = "_sip._tcp.foo.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { val test = srv.copy(name = "*._tcp.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { val test = srv.copy(name = "_sip._*.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } } "Skip dotted checks on NAPTR" should { @@ -335,21 +340,21 @@ class RecordSetValidationsSpec val test = naptr.copy(name = "sub.naptr.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { val test = naptr.copy(name = "sub.naptr") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } "return success on a wildcard NAPTR" in { val test = naptr.copy(name = "*.sub.naptr.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } } @@ -358,7 +363,7 @@ class RecordSetValidationsSpec val test = ptrIp4.copy(name = "10.1.2.") val zone = zoneIp4.copy(name = "198.in-addr.arpa.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } } "Skip dotted checks on TXT" should { @@ -366,7 +371,7 @@ class RecordSetValidationsSpec val test = txt.copy(name = "sub.txt.example.com.") val zone = okZone.copy(name = "example.com.") - typeSpecificValidations(test, List(), zone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } } @@ -383,7 +388,7 @@ class RecordSetValidationsSpec List(SOAData(Fqdn("something"), "other", 1, 2, 3, 5, 6)) ) - typeSpecificValidations(test, List(), zoneIp4, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + typeSpecificValidations(test, List(), zoneIp4, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } } } @@ -396,29 +401,29 @@ class RecordSetValidationsSpec records = List(NSData(Fqdn("some.test.ns."))) ) - nsValidations(valid, okZone, None, List(new Regex(".*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { - val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(nsValidations(invalidNsApexRs, okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if an NS record is the same as the zone" in { val invalid = invalidNsApexRs.copy(name = okZone.name) - val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(nsValidations(invalid, okZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the NS record being updated is '@'" in { val valid = invalidNsApexRs.copy(name = "this-is-not-origin-mate") - val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(nsValidations(valid, okZone, Some(invalidNsApexRs), Nil, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "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 error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(nsValidations(ns, okZone, None, List(new Regex("not.*")), true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } } @@ -426,35 +431,35 @@ class RecordSetValidationsSpec "DSValidations" should { 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 { - dsValidations(ds, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be(right) + dsValidations(ds, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false) should be(right) } "return an InvalidRequest if a DS record is '@'" in { val apex = ds.copy(name = "@") - val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a DS record is the same as the zone" in { val apex = ds.copy(name = okZone.name) - val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(dsValidations(apex, List(matchingNs), okZone, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if there is no NS matching the record" in { - val error = leftValue(dsValidations(ds, List(), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(dsValidations(ds, List(), okZone, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if the DS is dotted" in { val error = - leftValue(dsValidations(ds.copy(name = "test.dotted"), List(matchingNs), okZone, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + dsValidations(ds.copy(name = "dotted.trial", zoneId = dottedZone.id), List(matchingNs), dottedZone, true, dottedHostsConfigZonesAllowed.toSet, true) 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true)) + leftValue(dsValidations(ds.copy(name = "dotted.trial", zoneId = dottedZone.id), List(matchingNs), dottedZone, false, dottedHostsConfigZonesAllowed.toSet, true)) error shouldBe an[InvalidRequest] } } @@ -462,62 +467,62 @@ class RecordSetValidationsSpec "CnameValidations" should { val invalidCnameApexRs: RecordSet = cname.copy(name = "@") "return a RecordSetAlreadyExistsError if a record with the same name exists and creating a cname" in { - val error = leftValue(cnameValidations(cname, List(aaaa), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(cname, List(aaaa), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe a[RecordSetAlreadyExists] } "return ok if name is not '@'" in { - cnameValidations(cname, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) 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 { - val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(invalidCnameApexRs, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a cname record set name is same as zone" in { val invalid = invalidCnameApexRs.copy(name = okZone.name) - val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if a cname record set name is dotted" in { - val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return ok if new recordset name does not contain dot" in { - cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted")), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( + cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted")), true, dottedHostsConfigZonesAllowed.toSet, false) should be( right ) } "return ok if dotted host name doesn't change" in { val newRecord = cname.copy(name = "dot.ted", ttl = 500) - cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300)), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( + cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300)), true, dottedHostsConfigZonesAllowed.toSet, false) should be( right ) } "return an InvalidRequest if a cname record set name is updated to '@'" in { - val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname), true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "return an InvalidRequest if updated cname record set name is same as zone" in { val error = - leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname), true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + leftValue(cnameValidations(cname.copy(name = okZone.name), List(), okZone, Some(cname), true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[InvalidRequest] } "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, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false)) + val error = leftValue(cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record..zone")))), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false)) error shouldBe an[RecordSetValidation] } "return ok if recordset data does not contain sequential '.'" in { - cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone, None, true, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, false) should be( + cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("record.zone")))), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false) should be( 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true) + cnameValidations(cname.copy(name = "dot.ted", zoneId = dottedZone.id), List(), dottedZone, None, true, dottedHostsConfigZonesAllowed.toSet, true) 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, VinylDNSTestHelpers.dottedHostsConfig.zoneList.toSet, true)) + leftValue(cnameValidations(cname.copy(name = "dot.ted", zoneId = dottedZone.id), List(), dottedZone, None, false, dottedHostsConfigZonesAllowed.toSet, true)) error shouldBe an[InvalidRequest] } } diff --git a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala index cdc6cc925..34c1495f1 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala @@ -34,7 +34,7 @@ object TestZoneData { adminGroupId = okGroup.id, connection = testConnection ) - val dottedZone: Zone = Zone("dotted.xyz", "dotted@xyz.com", adminGroupId = xyzGroup.id) + val dottedZone: Zone = Zone("dotted.xyz.", "dotted@xyz.com", adminGroupId = xyzGroup.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 zoneIp4: Zone = Zone("0.162.198.in-addr.arpa.", "test@test.com", adminGroupId = abcGroup.id) From 228d2c169aec9c04017558dee0d23656ef780f81 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 22 Sep 2022 15:16:35 +0530 Subject: [PATCH 09/30] Update tests --- modules/api/src/main/resources/reference.conf | 24 +++-- .../tests/recordsets/create_recordset_test.py | 64 ++++++++++--- .../tests/shared_zone_test_context.py | 9 ++ .../vinyldns/api/VinylDNSTestHelpers.scala | 2 + .../domain/record/RecordSetServiceSpec.scala | 92 +++++++++++++++---- 5 files changed, 149 insertions(+), 42 deletions(-) diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index da75134ca..e353f10ce 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -93,14 +93,22 @@ vinyldns { # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { allowed-settings = [ - { - # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" - zone = "*ent.com*." - allowed-user-list = ["ok"] - allowed-group-list = ["dummy-group*"] - allowed-record-type = ["CNAME"] - } + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com*." + allowed-user-list = ["ok"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["CNAME"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "dummy*." + allowed-user-list = ["sharedZoneUser"] + allowed-group-list = ["history-group1"] + allowed-record-type = ["A"] + } ] } diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index b909627e9..514bf8c40 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -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 @@ -528,6 +530,33 @@ def test_create_dotted_a_record_not_apex_fails(shared_zone_test_context): "is not allowed in zone " + zone_name)) +def test_create_dotted_a_record_succeeds_if_all_dotted_hosts_config_satisfies(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_apex_succeeds(shared_zone_test_context): """ Test that creating an apex A record set containing dots succeeds. @@ -581,13 +610,15 @@ def test_create_dotted_a_record_apex_with_trailing_dot_succeeds(shared_zone_test 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.dummy_vinyldns_client zone = shared_zone_test_context.dummy_zone - apex_cname_rs = { + dotted_host_cname_record = { "zoneId": zone["id"], "name": "dot.ted", "type": "CNAME", @@ -595,18 +626,20 @@ def test_create_dotted_cname_record_fails(shared_zone_test_context): "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"]}')) def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfies(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 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 - apex_cname_rs = { + dotted_host_cname_record = { "zoneId": zone["id"], "name": "dot.ted", "type": "CNAME", @@ -614,14 +647,14 @@ def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfie "records": [{"cname": "foo.bar."}] } - apex_cname_record = None + dotted_cname_record = None try: - apex_cname_response = client.create_recordset(apex_cname_rs, status=202) - apex_cname_record = client.wait_until_recordset_change_status(apex_cname_response, "Complete")["recordSet"] - assert_that(apex_cname_record["name"], is_(apex_cname_rs["name"])) + 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 apex_cname_record: - delete_result = client.delete_recordset(apex_cname_record["zoneId"], apex_cname_record["id"], status=202) + 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") @@ -2099,7 +2132,8 @@ def test_create_apex_ds_fails(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client 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) 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')) diff --git a/modules/api/src/test/functional/tests/shared_zone_test_context.py b/modules/api/src/test/functional/tests/shared_zone_test_context.py index 8d919cb23..1206d608c 100644 --- a/modules/api/src/test/functional/tests/shared_zone_test_context.py +++ b/modules/api/src/test/functional/tests/shared_zone_test_context.py @@ -174,6 +174,15 @@ class SharedZoneTestContext(object): "shared": False, "adminGroupId": self.dummy_group["id"], "isTest": True, + "acl": { + "rules": [ + { + "accessLevel": "Delete", + "description": "some_test_rule", + "userId": "history-id" + } + ] + }, "connection": { "name": "dummy.", "keyName": VinylDNSTestContext.dns_key_name, diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index 24f0ae8c4..80b88abbb 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -42,6 +42,8 @@ trait VinylDNSTestHelpers { val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(AuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME")), AuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME")), AuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME")))) + val emptyDottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List.empty) + val defaultTtl: Long = 7200 val manualReviewDomainList: List[Regex] = List(new Regex("needs-review.*")) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 7174a2c72..f5257eaa0 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -108,6 +108,25 @@ class RecordSetServiceSpec 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.authMethods.map { case x: AuthConfigs => x.zone @@ -148,7 +167,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -218,7 +237,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -243,6 +262,41 @@ class RecordSetServiceSpec val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) 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(Set.empty) + 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 { val record = aaaa.copy(name = "new.", zoneId = okZone.id, status = RecordSetStatus.Active) @@ -253,7 +307,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -299,7 +353,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -338,7 +392,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -396,7 +450,7 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(okGroup))) .when(mockGroupRepo) .getGroup(okGroup.id) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -470,7 +524,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -522,7 +576,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(dottedZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -568,7 +622,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(xyzZone.id, record.name) - doReturn(IO.pure(Set(xyzZone))) + doReturn(IO.pure(Set(xyzZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -607,7 +661,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -650,7 +704,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(abcZone.id, record.name) - doReturn(IO.pure(Set(abcZone))) + doReturn(IO.pure(Set(abcZone, dottedZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -693,7 +747,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(dottedZone.id, record.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -734,7 +788,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -791,7 +845,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -851,7 +905,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -894,7 +948,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -937,7 +991,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -1093,7 +1147,7 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(oneUserDummyGroup))) .when(mockGroupRepo) .getGroup(oneUserDummyGroup.id) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -1143,7 +1197,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List(oldRecord))) .when(mockRecordRepo) .getRecordSetsByName(zone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) From 204ce5f93925fde05dd78b47e2f6e00ea6ffe29f Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 22 Sep 2022 16:42:46 +0530 Subject: [PATCH 10/30] Add it test --- .../RecordSetServiceIntegrationSpec.scala | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index 1cf0d50f6..544632028 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -32,7 +32,6 @@ import vinyldns.api.domain.access.AccessValidations import vinyldns.api.domain.zone._ import vinyldns.api.engine.TestMessageQueue import vinyldns.mysql.TransactionProvider -import vinyldns.core.TestMembershipData._ import vinyldns.core.TestZoneData.testConnection import vinyldns.core.domain.{Fqdn, HighValueDomainError} import vinyldns.core.domain.auth.AuthPrincipal @@ -64,13 +63,16 @@ class RecordSetServiceIntegrationSpec private var testRecordSetService: RecordSetServiceAlgebra = _ 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 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 sharedGroup = 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 auth2 = AuthPrincipal(user2, Seq(sharedGroup.id, group2.id)) + val dummyAuth: AuthPrincipal = AuthPrincipal(testUser, Seq(dummyGroup.id)) private val zone = Zone( s"live-zone-test.", @@ -167,6 +169,14 @@ class RecordSetServiceIntegrationSpec adminGroupId = group.id ) + private val dummyZone = Zone( + s"dummy.", + "test@test.com", + status = ZoneStatus.Active, + connection = testConnection, + adminGroupId = dummyGroup.id + ) + private val highValueDomainRecord = RecordSet( zone.id, "high-value-domain-existing", @@ -254,8 +264,8 @@ class RecordSetServiceIntegrationSpec groupRepo.save(db, group) } - List(group, group2, sharedGroup).traverse(g => saveGroupData(groupRepo, g).void).unsafeRunSync() - List(zone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone) + List(group, group2, sharedGroup, dummyGroup).traverse(g => saveGroupData(groupRepo, g).void).unsafeRunSync() + List(zone, dummyZone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone) .traverse( z => zoneRepo.save(z) ) @@ -339,6 +349,48 @@ class RecordSetServiceIntegrationSpec .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" + } + "update apex A record and add trailing dot" in { val newRecord = apexTestRecordA.copy(ttl = 200) val result = testRecordSetService From e4ad55e5f77c39e19be05d2e190258869ff6ec3c Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 23 Sep 2022 12:16:53 +0530 Subject: [PATCH 11/30] Add documentation --- modules/api/src/it/resources/application.conf | 22 +++++++ .../api/src/main/resources/application.conf | 25 ++------ modules/api/src/main/resources/reference.conf | 32 +++++----- .../docs/src/main/mdoc/operator/config-api.md | 64 +++++++++++++++++++ 4 files changed, 106 insertions(+), 37 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index a64eddac2..8d506c8e7 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -163,6 +163,28 @@ vinyldns { "ns1.parent.com4." ] + # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + dotted-hosts = { + # for local testing + allowed-settings = [ + { + type = "auth-configs" + zone = "dummy." + allowed-user-list = ["testuser"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["AAAA"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com." + allowed-user-list = ["professor", "testuser"] + allowed-group-list = ["testing-group"] + allowed-record-type = ["A", "CNAME"] + } + ] + } + # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production crypto { type = "vinyldns.core.crypto.NoOpCrypto" diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 073b7065a..1667d9298 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,27 +165,10 @@ vinyldns { "ns1.parent.com4." ] -# approved zones, individual users, users in groups and record types that are allowed for dotted hosts -dotted-hosts = { - # for local testing - allowed-settings = [ - { - type = "auth-configs" - zone = "dummy." - allowed-user-list = ["testuser"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["AAAA"] - }, - { - # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" - zone = "*ent.com." - allowed-user-list = ["professor", "testuser"] - allowed-group-list = ["testing-group"] - allowed-record-type = ["A", "CNAME"] - } - ] -} + # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + dotted-hosts = { + allowed-settings = [] + } # Note: This MUST match the Portal or strange errors will ensue, NoOpCrypto should not be used for production crypto { diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index e353f10ce..a93327696 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -93,22 +93,22 @@ vinyldns { # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { allowed-settings = [ - { - # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" - zone = "*ent.com*." - allowed-user-list = ["ok"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["CNAME"] - }, - { - # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" - zone = "dummy*." - allowed-user-list = ["sharedZoneUser"] - allowed-group-list = ["history-group1"] - allowed-record-type = ["A"] - } + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com*." + allowed-user-list = ["ok"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["CNAME"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "dummy*." + allowed-user-list = ["sharedZoneUser"] + allowed-group-list = ["history-group1"] + allowed-record-type = ["A"] + } ] } diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 2d820849a..d62bd0699 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -536,7 +536,50 @@ v6-discovery-nibble-boundaries { min = 5 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. The config `type = "auth-configs"` is a default which shouldn't be changed. +2. Zones defined in the `zone` must always end with a dot. Eg: `comcast.com.` +3. Wildcard character `*` can be used in `zone` to allow dotted hosts for all zones matching it. +4. Individual users who are allowed to create dotted hosts are added to the `allowed-user-list` using their username. +5. A set of users in a group who are allowed to create dotted hosts are added to the `allowed-group-list` using group name. +6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. + +```yaml +# approved zones, individual users, users in groups and record types that are allowed for dotted hosts +dotted-hosts = { + allowed-settings = [ + { + type = "auth-configs" + zone = "dummy." + allowed-user-list = ["testuser"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["AAAA"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com." + allowed-user-list = ["professor", "testuser"] + allowed-group-list = ["testing-group"] + allowed-record-type = ["A", "CNAME"] + } + ] +} +``` + +The config can be left empty as follows if we don't want to use it: + +```yaml +dotted-hosts = { + allowed-settings = [] +} ``` ### Full Example Config @@ -713,6 +756,27 @@ v6-discovery-nibble-boundaries { } } + # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + dotted-hosts = { + allowed-settings = [ + { + type = "auth-configs" + zone = "dummy." + allowed-user-list = ["testuser"] + allowed-group-list = ["dummy-group"] + allowed-record-type = ["AAAA"] + }, + { + # for wildcard zones. Settings will be applied to all matching zones + type = "auth-configs" + zone = "*ent.com." + allowed-user-list = ["professor", "testuser"] + allowed-group-list = ["testing-group"] + allowed-record-type = ["A", "CNAME"] + } + ] + } + # true if you want to enable manual review for non-fatal errors manual-batch-review-enabled = true From d47d2e1a1de128d238d90410105c26bd1af5e225 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 23 Sep 2022 13:17:08 +0530 Subject: [PATCH 12/30] Update documentation --- modules/api/src/main/resources/reference.conf | 1 + modules/docs/src/main/mdoc/operator/config-api.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index a93327696..0ad8da47f 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -92,6 +92,7 @@ vinyldns { # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { + # for local testing allowed-settings = [ { # for wildcard zones. Settings will be applied to all matching zones diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index d62bd0699..6c462f8ae 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -574,6 +574,12 @@ dotted-hosts = { } ``` +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, 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 `allowed-record-type` + The config can be left empty as follows if we don't want to use it: ```yaml From 97d9a2c3bb1571d7108678238f746e9e9a45fc88 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 23 Sep 2022 13:38:53 +0530 Subject: [PATCH 13/30] Remove unused changes --- .../vinyldns/api/domain/record/RecordSetValidations.scala | 2 +- .../scala/vinyldns/core/domain/DomainValidationErrors.scala | 6 ------ .../main/scala/vinyldns/core/domain/SingleChangeError.scala | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 1d7c3a427..a620b3afd 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -123,7 +123,7 @@ object RecordSetValidations { ensuring( InvalidRequest( s"Record with fqdn '${newRecordSet.name}.${zone.name}' cannot be created. " + - s"Please check if user has permission or if there's a zone/record that already exist and make the change there." + s"Please check if there's 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 diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index cea5fb3df..c258ad975 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -121,12 +121,6 @@ final case class ZoneDiscoveryError(name: String, fatal: Boolean = false) "If zone exists, then it must be connected to in VinylDNS." } -final case class DottedHostError(name: String, zone: String) extends DomainValidationError { - def message: String = - s"Record with fqdn '$name.$zone' cannot be created. " + - s"Please check if user has permission or if there's a zone/record that already exist and make the change there." -} - final case class RecordAlreadyExists(name: String) extends DomainValidationError { def message: String = s"""Record "$name" Already Exists: cannot add an existing record; to update it, """ + diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index 63f1e33a9..c7805e74c 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -32,7 +32,7 @@ object DomainValidationErrorType extends Enumeration { val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidCname, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMxPreference, - InvalidBatchRecordType, ZoneDiscoveryError, DottedHostError, RecordAlreadyExists, RecordDoesNotExist, + InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, @@ -58,7 +58,6 @@ object DomainValidationErrorType extends Enumeration { case _: InvalidMxPreference => InvalidMxPreference case _: InvalidBatchRecordType => InvalidBatchRecordType case _: ZoneDiscoveryError => ZoneDiscoveryError - case _: DottedHostError => DottedHostError case _: RecordAlreadyExists => RecordAlreadyExists case _: RecordDoesNotExist => RecordDoesNotExist case _: CnameIsNotUniqueError => CnameIsNotUniqueError From 4cf0d8a9a8015297d1ed0d091be95f41e13b44c0 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 23 Sep 2022 13:57:43 +0530 Subject: [PATCH 14/30] Update doc --- modules/docs/src/main/mdoc/operator/config-api.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 6c462f8ae..338a2df7c 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -549,7 +549,9 @@ Note the following: 3. Wildcard character `*` can be used in `zone` to allow dotted hosts for all zones matching it. 4. Individual users who are allowed to create dotted hosts are added to the `allowed-user-list` using their username. 5. A set of users in a group who are allowed to create dotted hosts are added to the `allowed-group-list` using group name. -6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. +6. If the user is either in `allowed-user-list` or `allowed-group-list`, they are allowed to create a dotted host. It is +not necessary for the user to be in both `allowed-user-list` and `allowed-group-list`. +7. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. ```yaml # approved zones, individual users, users in groups and record types that are allowed for dotted hosts @@ -576,7 +578,7 @@ dotted-hosts = { 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, group users and record type of the respective zone to create a dotted host. +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 `allowed-record-type` From 371404a5e5c5221d977af9970fc8012e5ae9b716 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 23 Sep 2022 14:02:11 +0530 Subject: [PATCH 15/30] Update doc --- modules/docs/src/main/mdoc/operator/config-api.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 338a2df7c..4038bdb85 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -580,7 +580,9 @@ In the above, the dotted hosts can be created only in the zone `dummy.` and zone 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 `allowed-record-type` +For eg, we can't create a dotted host with `CNAME` record type in the zone `dummy.` as it's not in `allowed-record-type`. +And the user `professor` can't create a dotted host in the zone `dummy.` as the user is not in `allowed-user-list` or +`allowed-group-list`. The config can be left empty as follows if we don't want to use it: From 18333fe3ab09f6f972fef6884bbfa14269ae735d Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 26 Sep 2022 11:57:27 +0530 Subject: [PATCH 16/30] Update config --- modules/api/src/it/resources/application.conf | 2 - modules/api/src/main/resources/reference.conf | 2 - .../api/config/DottedHostsConfig.scala | 11 ++-- .../api/domain/record/RecordSetService.scala | 65 +++++++++---------- .../domain/record/RecordSetServiceSpec.scala | 37 ++++------- .../record/RecordSetValidationsSpec.scala | 5 +- .../docs/src/main/mdoc/operator/config-api.md | 19 ++---- 7 files changed, 56 insertions(+), 85 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index 8d506c8e7..e14cb169d 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -168,7 +168,6 @@ vinyldns { # for local testing allowed-settings = [ { - type = "auth-configs" zone = "dummy." allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] @@ -176,7 +175,6 @@ vinyldns { }, { # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" zone = "*ent.com." allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 0ad8da47f..16c7724f4 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -96,7 +96,6 @@ vinyldns { allowed-settings = [ { # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" zone = "*ent.com*." allowed-user-list = ["ok"] allowed-group-list = ["dummy-group"] @@ -104,7 +103,6 @@ vinyldns { }, { # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" zone = "dummy*." allowed-user-list = ["sharedZoneUser"] allowed-group-list = ["history-group1"] diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index 183b1b4e0..3e4429898 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -19,14 +19,13 @@ package vinyldns.api.config import pureconfig.ConfigReader import pureconfig.generic.auto._ -sealed trait AuthMethod -final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) extends AuthMethod -final case class DottedHostsConfig(authMethods: List[AuthMethod]) +final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) +final case class DottedHostsConfig(authConfigs: List[AuthConfigs]) object DottedHostsConfig { implicit val configReader: ConfigReader[DottedHostsConfig] = - ConfigReader.forProduct1[DottedHostsConfig, List[AuthMethod]]( + ConfigReader.forProduct1[DottedHostsConfig, List[AuthConfigs]]( "allowed-settings", - )(authMethods => - DottedHostsConfig(authMethods)) + )(authConfigs => + DottedHostsConfig(authConfigs)) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 210d26937..309b1054d 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -110,9 +110,7 @@ class RecordSetService( ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - authZones = dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + authZones = dottedHostsConfig.authConfigs.map(x => x.zone) allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]] isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth) isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean] @@ -159,9 +157,7 @@ class RecordSetService( validateRecordLookupAgainstDnsBackend ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - authZones = dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + authZones = dottedHostsConfig.authConfigs.map(x => x.zone) allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]] isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth) isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean] @@ -242,21 +238,20 @@ class RecordSetService( // 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.authMethods.map{ - case x: AuthConfigs => x.zone - } + val configZones = config.authConfigs.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(isContainWildcardZone || isContainNormalZone){ - val users = config.authMethods.flatMap { - case x: AuthConfigs => if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if(zoneName.matches(wildcardZone)) x.allowedUserList else List.empty - } else { - if (x.zone == zoneName) x.allowedUserList else List.empty - } + val users = config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if (zoneName.matches(wildcardZone)) x.allowedUserList else List.empty + } else { + if (x.zone == zoneName) x.allowedUserList else List.empty + } } if(users.contains(auth.signedInUser.userName)){ true @@ -272,21 +267,20 @@ class RecordSetService( // Check if user is allowed to create dotted hosts using the users present in dotted hosts config def checkIfInAllowedRecordType(zone: Zone, config: DottedHostsConfig, rs: RecordSet): Boolean = { - val configZones = config.authMethods.map{ - case x: AuthConfigs => x.zone - } + val configZones = config.authConfigs.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(isContainWildcardZone || isContainNormalZone){ - val rType = config.authMethods.flatMap { - case x: AuthConfigs => if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if(zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty - } else { - if (x.zone == zoneName) x.allowedRecordType else List.empty - } + val rType = config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if (zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty + } else { + if (x.zone == zoneName) x.allowedRecordType else List.empty + } } if(rType.contains(rs.typ.toString)){ true @@ -302,21 +296,20 @@ class RecordSetService( // 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.authMethods.map{ - case x: AuthConfigs => x.zone - } + val configZones = config.authConfigs.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(isContainWildcardZone || isContainNormalZone){ - config.authMethods.flatMap { - case x: AuthConfigs => if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if(zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty - } else { - if (x.zone == zoneName) x.allowedGroupList else List.empty - } + config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if (zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty + } else { + if (x.zone == zoneName) x.allowedGroupList else List.empty + } } } else { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index f5257eaa0..05978d393 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -128,21 +128,20 @@ class RecordSetServiceSpec ) def getDottedHostsConfigGroupsAllowed(zone: Zone, config: DottedHostsConfig): List[String] = { - val configZones = config.authMethods.map { - case x: AuthConfigs => x.zone - } + val configZones = config.authConfigs.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.authMethods.flatMap { - case x: AuthConfigs => if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z.]*") - if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.allowedGroupList else List.empty - } else { - if (x.zone == zoneName) x.allowedGroupList else List.empty - } + config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z.]*") + if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.allowedGroupList else List.empty + } else { + if (x.zone == zoneName) x.allowedGroupList else List.empty + } } } else { @@ -151,9 +150,7 @@ class RecordSetServiceSpec groups } - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(okZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -563,9 +560,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -609,9 +604,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = xyzZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(xyzZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -691,9 +684,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = abcZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(abcZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -734,7 +725,7 @@ class RecordSetServiceSpec val record = aaaa.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map { case y:AuthConfigs => y.zone } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index f39600191..f07efa589 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -25,7 +25,6 @@ import vinyldns.core.domain.record.RecordType._ import vinyldns.api.domain.zone.{InvalidGroupError, InvalidRequest, PendingUpdateError, RecordSetAlreadyExists, RecordSetValidation} import vinyldns.api.ResultHelpers import vinyldns.api.VinylDNSTestHelpers -import vinyldns.api.config.AuthConfigs import vinyldns.core.TestRecordSetData._ import vinyldns.core.TestZoneData._ import vinyldns.core.TestMembershipData._ @@ -45,9 +44,7 @@ class RecordSetValidationsSpec import RecordSetValidations._ - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authMethods.map { - case y:AuthConfigs => y.zone - } + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) "RecordSetValidations" should { "validRecordTypes" should { diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 4038bdb85..576dd63aa 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -543,22 +543,20 @@ v6-discovery-nibble-boundaries { 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. The config `type = "auth-configs"` is a default which shouldn't be changed. -2. Zones defined in the `zone` must always end with a dot. Eg: `comcast.com.` -3. Wildcard character `*` can be used in `zone` to allow dotted hosts for all zones matching it. -4. Individual users who are allowed to create dotted hosts are added to the `allowed-user-list` using their username. -5. A set of users in a group who are allowed to create dotted hosts are added to the `allowed-group-list` using group name. -6. If the user is either in `allowed-user-list` or `allowed-group-list`, they are allowed to create a dotted host. It is +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 `allowed-user-list` using their username. +4. A set of users in a group who are allowed to create dotted hosts are added to the `allowed-group-list` using group name. +5. If the user is either in `allowed-user-list` or `allowed-group-list`, they are allowed to create a dotted host. It is not necessary for the user to be in both `allowed-user-list` and `allowed-group-list`. -7. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. +6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. ```yaml # approved zones, individual users, users in groups and record types that are allowed for dotted hosts dotted-hosts = { allowed-settings = [ { - type = "auth-configs" zone = "dummy." allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] @@ -566,7 +564,6 @@ dotted-hosts = { }, { # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" zone = "*ent.com." allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] @@ -770,7 +767,6 @@ dotted-hosts = { dotted-hosts = { allowed-settings = [ { - type = "auth-configs" zone = "dummy." allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] @@ -778,7 +774,6 @@ dotted-hosts = { }, { # for wildcard zones. Settings will be applied to all matching zones - type = "auth-configs" zone = "*ent.com." allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] From 524525e67b333f93fa35273b570182fb95445fc6 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 3 Oct 2022 14:22:21 +0530 Subject: [PATCH 17/30] Address PR comments --- .../api/domain/record/RecordSetService.scala | 7 ++-- .../domain/record/RecordSetValidations.scala | 32 ++++++++++++++++--- .../tests/recordsets/create_recordset_test.py | 8 ++--- .../zones/zoneTabs/manageRecords.scala.html | 2 +- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 309b1054d..44d745584 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -91,10 +91,12 @@ class RecordSetService( def addRecordSet(recordSet: RecordSet, auth: AuthPrincipal): Result[ZoneCommandResult] = for { zone <- getZone(recordSet.zoneId) - change <- RecordSetChangeGenerator.forAdd(recordSet, zone, Some(auth)).toResult + authZones = dottedHostsConfig.authConfigs.map(x => x.zone) + newRs = if(authZones.contains(zone.name) && recordSet.name.takeRight(1) == ".") recordSet.copy(name = recordSet.name.dropRight(1)) else recordSet + change <- RecordSetChangeGenerator.forAdd(newRs, zone, Some(auth)).toResult // because changes happen to the RS in forAdd itself, converting 1st and validating on that rsForValidations = change.recordSet - _ <- isNotHighValueDomain(recordSet, zone, highValueDomainConfig).toResult + _ <- isNotHighValueDomain(newRs, zone, highValueDomainConfig).toResult _ <- recordSetDoesNotExist( backendResolver.resolve, zone, @@ -110,7 +112,6 @@ class RecordSetService( ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - authZones = dottedHostsConfig.authConfigs.map(x => x.zone) allowedZoneList <- getAllowedZones(authZones).toResult[Set[String]] isInAllowedUsers = checkIfInAllowedUsers(zone, dottedHostsConfig, auth) isUserInAllowedGroups <- checkIfInAllowedGroups(zone, dottedHostsConfig, auth).toResult[Boolean] diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index a620b3afd..d12a4f3ea 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -101,18 +101,24 @@ object RecordSetValidations { ): Either[Throwable, Unit] = { val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name - // Check if the zone of the record set is present in dotted hosts config list + // 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((newRecordSet.name.contains(".") || !recordFqdnDoesNotExist) && isDomainAllowed && isRecordTypeAndUserAllowed && newRecordSet.name != zone.name) { - isDotted(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, isRecordTypeAndUserAllowed) + if(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) } } - // Check if the user is not authorized and if there is a zone/record already present which conflicts with the new dotted record. If so, throw an error + // 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, @@ -129,6 +135,22 @@ object RecordSetValidations { (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( newRecordSet: RecordSet, @@ -151,7 +173,7 @@ object RecordSetValidations { zone: Zone, existingRecordSet: Option[RecordSet], approvedNameServers: List[Regex], - recordFqdnDoesNotExist: Boolean = true, + recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean ): Either[Throwable, Unit] = diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index 514bf8c40..f3f768777 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -526,8 +526,8 @@ def test_create_dotted_a_record_not_apex_fails_when_dotted_hosts_config_not_sati zone_name = shared_zone_test_context.parent_zone["name"] 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 " - "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_a_record_succeeds_if_all_dotted_hosts_config_satisfies(shared_zone_test_context): @@ -627,8 +627,8 @@ def test_create_dotted_cname_record_fails_when_dotted_hosts_config_not_satisfied } 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_satisfies(shared_zone_test_context): diff --git a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html index 850bd8a81..61272b245 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html @@ -122,7 +122,7 @@
+ title="This is a dotted host!"> {{record.name}}
From 5950a84215c62cf1730153742cf6fbb0d5e4fb78 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 7 Oct 2022 15:13:32 +0530 Subject: [PATCH 19/30] Address PR comments --- .../scala/vinyldns/api/domain/record/RecordSetService.scala | 2 +- .../vinyldns/api/domain/record/RecordSetValidations.scala | 2 +- .../functional/tests/recordsets/create_recordset_test.py | 4 ++-- .../api/domain/record/RecordSetValidationsSpec.scala | 6 ------ modules/docs/src/main/mdoc/operator/config-api.md | 2 +- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 44d745584..8041a3320 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -266,7 +266,7 @@ class RecordSetService( } } - // Check if user is allowed to create dotted hosts using the users present in dotted hosts config + // 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.authConfigs.map(x => x.zone) val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index d12a4f3ea..58e35b518 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -129,7 +129,7 @@ object RecordSetValidations { ensuring( InvalidRequest( s"Record with fqdn '${newRecordSet.name}.${zone.name}' cannot be created. " + - s"Please check if there's a record with the same fqdn and type already exist and make the change there." + 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 diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index f3f768777..d3705298d 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -530,7 +530,7 @@ def test_create_dotted_a_record_not_apex_fails_when_dotted_hosts_config_not_sati "zone '" + zone_name + "'")) -def test_create_dotted_a_record_succeeds_if_all_dotted_hosts_config_satisfies(shared_zone_test_context): +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 @@ -631,7 +631,7 @@ def test_create_dotted_cname_record_fails_when_dotted_hosts_config_not_satisfied "zone '" + zone["name"] + "'")) -def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfies(shared_zone_test_context): +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 diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index f07efa589..10fd0a99e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -197,12 +197,6 @@ class RecordSetValidationsSpec leftValue(isDotted(test, okZone, None, true, false)) shouldBe an[InvalidRequest] } - "return a failure for a dotted record name that matches the zone name" in { - val test = aaaa.copy(name = "ok.www.comcast.net") - val zone = okZone.copy(name = "ok.www.comcast.net") - leftValue(isDotted(test, zone, None, true, true)) 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) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 576dd63aa..c1ad37b6e 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -579,7 +579,7 @@ Also, it must satisfy the allowed users or group users and record type of the re For eg, we can't create a dotted host with `CNAME` record type in the zone `dummy.` as it's not in `allowed-record-type`. And the user `professor` can't create a dotted host in the zone `dummy.` as the user is not in `allowed-user-list` or -`allowed-group-list`. +`allowed-group-list` (not part of `dummy-group`). The config can be left empty as follows if we don't want to use it: From 91dea767741b935cf8278dcac44372992ad4fced Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 7 Oct 2022 16:02:03 +0530 Subject: [PATCH 20/30] Address PR comments --- .../api/domain/record/RecordSetService.scala | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 8041a3320..f2e769c79 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -244,15 +244,10 @@ class RecordSetService( 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(isContainWildcardZone || isContainNormalZone){ + if(isContainNormalZone){ val users = config.authConfigs.flatMap { x: AuthConfigs => - if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if (zoneName.matches(wildcardZone)) x.allowedUserList else List.empty - } else { - if (x.zone == zoneName) x.allowedUserList else List.empty - } + if (x.zone == zoneName) x.allowedUserList else List.empty } if(users.contains(auth.signedInUser.userName)){ true @@ -261,7 +256,22 @@ class RecordSetService( false } } - else{ + else if(isContainWildcardZone){ + val users = config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if (zoneName.matches(wildcardZone)) x.allowedUserList else List.empty + } else List.empty + } + if(users.contains(auth.signedInUser.userName)){ + true + } + else { + false + } + } + else { false } } @@ -273,15 +283,10 @@ class RecordSetService( 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(isContainWildcardZone || isContainNormalZone){ + if(isContainNormalZone){ val rType = config.authConfigs.flatMap { x: AuthConfigs => - if (x.zone.contains("*")) { - val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if (zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty - } else { - if (x.zone == zoneName) x.allowedRecordType else List.empty - } + if (x.zone == zoneName) x.allowedRecordType else List.empty } if(rType.contains(rs.typ.toString)){ true @@ -290,7 +295,22 @@ class RecordSetService( false } } - else{ + else if(isContainWildcardZone){ + val rType = config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone.contains("*")) { + val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") + if (zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty + } else List.empty + } + if(rType.contains(rs.typ.toString)){ + true + } + else { + false + } + } + else { false } } @@ -302,15 +322,19 @@ class RecordSetService( 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(isContainWildcardZone || isContainNormalZone){ + val groups = if(isContainNormalZone){ + config.authConfigs.flatMap { + x: AuthConfigs => + if (x.zone == zoneName) x.allowedGroupList else List.empty + } + } + else if(isContainWildcardZone){ config.authConfigs.flatMap { x: AuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") if (zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty - } else { - if (x.zone == zoneName) x.allowedGroupList else List.empty - } + } else List.empty } } else { From a60991a073b79d0b9650004dae8e7aca3d7a5edc Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 7 Oct 2022 19:17:01 +0530 Subject: [PATCH 21/30] Address PR comments --- modules/api/src/it/resources/application.conf | 2 + modules/api/src/main/resources/reference.conf | 2 + .../api/config/DottedHostsConfig.scala | 2 +- .../api/domain/record/RecordSetService.scala | 20 +++++ .../domain/record/RecordSetValidations.scala | 13 +++ .../tests/recordsets/create_recordset_test.py | 80 ++++++++++++++----- .../vinyldns/api/VinylDNSTestHelpers.scala | 2 +- .../docs/src/main/mdoc/operator/config-api.md | 5 ++ 8 files changed, 105 insertions(+), 21 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index e14cb169d..876d39527 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -172,6 +172,7 @@ vinyldns { allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] allowed-record-type = ["AAAA"] + allowed-dots-limit = 3 }, { # for wildcard zones. Settings will be applied to all matching zones @@ -179,6 +180,7 @@ vinyldns { allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] allowed-record-type = ["A", "CNAME"] + allowed-dots-limit = 3 } ] } diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 16c7724f4..021ad67ce 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -100,6 +100,7 @@ vinyldns { allowed-user-list = ["ok"] allowed-group-list = ["dummy-group"] allowed-record-type = ["CNAME"] + allowed-dots-limit = 3 }, { # for wildcard zones. Settings will be applied to all matching zones @@ -107,6 +108,7 @@ vinyldns { allowed-user-list = ["sharedZoneUser"] allowed-group-list = ["history-group1"] allowed-record-type = ["A"] + allowed-dots-limit = 3 } ] } diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index 3e4429898..e28f35f3b 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -19,7 +19,7 @@ package vinyldns.api.config import pureconfig.ConfigReader import pureconfig.generic.auto._ -final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String]) +final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String], allowedDotsLimit: Int) final case class DottedHostsConfig(authConfigs: List[AuthConfigs]) object DottedHostsConfig { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index f2e769c79..cfd51c9ba 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -118,6 +118,7 @@ class RecordSetService( isAllowedUser = isInAllowedUsers || isUserInAllowedGroups isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed + allowedDotsLimit = getAllowedDotsLimit(zone, dottedHostsConfig) recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -129,6 +130,7 @@ class RecordSetService( allowedZoneList, isRecordTypeAndUserAllowed ).toResult + _ <- checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -237,6 +239,24 @@ class RecordSetService( } } + // 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.authConfigs.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.authConfigs.filter(x => x.zone == zoneName).head.allowedDotsLimit + } + else if(isContainWildcardZone){ + config.authConfigs.filter(x => zoneName.matches(x.zone.replace("*", "[A-Za-z0-9.]*"))).head.allowedDotsLimit + } + 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.authConfigs.map(x => x.zone) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 58e35b518..3e59c9088 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -352,6 +352,19 @@ object RecordSetValidations { .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, 'allowed-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 canUseOwnerGroup( ownerGroupId: Option[String], group: Option[Group], diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index d3705298d..d6c9a6cff 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -557,6 +557,28 @@ def test_create_dotted_a_record_succeeds_if_all_dotted_hosts_config_satisfied(sh 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 'allowed-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, 'allowed-dots-limit = 3'.")) + + def test_create_dotted_a_record_apex_succeeds(shared_zone_test_context): """ Test that creating an apex A record set containing dots succeeds. @@ -654,7 +676,8 @@ def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfie 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) + delete_result = client.delete_recordset(dotted_cname_record["zoneId"], dotted_cname_record["id"], + status=202) client.wait_until_recordset_change_status(delete_result, "Complete") @@ -760,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"] 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: if a_record: delete_result = client.delete_recordset(a_record["zoneId"], a_record["id"], status=202) @@ -803,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"] 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: if cname_record: delete_result = client.delete_recordset(cname_record["zoneId"], cname_record["id"], status=202) @@ -1800,7 +1825,8 @@ def test_create_high_value_domain_fails(shared_zone_test_context): } 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): @@ -1822,7 +1848,8 @@ def test_create_high_value_domain_fails_case_insensitive(shared_zone_test_contex } 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): @@ -1843,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) - 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): @@ -1864,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) - 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): @@ -1931,7 +1960,8 @@ def test_create_with_owner_group_in_private_zone_by_acl_passes(shared_zone_test_ finally: clear_ok_acl_rules(shared_zone_test_context) 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") @@ -1957,8 +1987,11 @@ def test_create_with_owner_group_in_shared_zone_by_acl_passes(shared_zone_test_c finally: clear_shared_zone_acl_rules(shared_zone_test_context) if create_rs: - delete_result = shared_zone_test_context.shared_zone_vinyldns_client.delete_recordset(zone["id"], create_rs["id"], status=202) - shared_zone_test_context.shared_zone_vinyldns_client.wait_until_recordset_change_status(delete_result, "Complete") + delete_result = shared_zone_test_context.shared_zone_vinyldns_client.delete_recordset(zone["id"], + 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): @@ -2012,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 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"] 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): @@ -2054,7 +2089,8 @@ def test_create_ds_success(shared_zone_test_context): zone = shared_zone_test_context.ds_zone record_data = [ {"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) result_rs = None @@ -2096,7 +2132,8 @@ def test_create_ds_unknown_algorithm(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client 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) errors = client.create_recordset(record_json, status=400)["errors"] assert_that(errors, contains_inanyorder("Algorithm 0 is not a supported DNSSEC algorithm")) @@ -2108,7 +2145,8 @@ def test_create_ds_unknown_digest_type(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client 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) 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")) @@ -2120,10 +2158,12 @@ def test_create_ds_no_ns_fails(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client 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) 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): @@ -2145,7 +2185,9 @@ def test_create_dotted_ds_fails(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client 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) 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"]}')) diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index 80b88abbb..08c4d28e1 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -40,7 +40,7 @@ trait VinylDNSTestHelpers { val approvedNameServers: List[Regex] = List(new Regex("some.test.ns.")) - val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(AuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME")), AuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME")), AuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME")))) + val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(AuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME"), 3), AuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME"), 3), AuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME"), 3))) val emptyDottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List.empty) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index c1ad37b6e..d32df144d 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -551,6 +551,7 @@ Note the following: 5. If the user is either in `allowed-user-list` or `allowed-group-list`, they are allowed to create a dotted host. It is not necessary for the user to be in both `allowed-user-list` and `allowed-group-list`. 6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. +7. The number of dots allowed in a record name for a zone is given in `allowed-dots-limit`. ```yaml # approved zones, individual users, users in groups and record types that are allowed for dotted hosts @@ -561,6 +562,7 @@ dotted-hosts = { allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] allowed-record-type = ["AAAA"] + allowed-dots-limit = 3 }, { # for wildcard zones. Settings will be applied to all matching zones @@ -568,6 +570,7 @@ dotted-hosts = { allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] allowed-record-type = ["A", "CNAME"] + allowed-dots-limit = 3 } ] } @@ -771,6 +774,7 @@ dotted-hosts = { allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] allowed-record-type = ["AAAA"] + allowed-dots-limit = 3 }, { # for wildcard zones. Settings will be applied to all matching zones @@ -778,6 +782,7 @@ dotted-hosts = { allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] allowed-record-type = ["A", "CNAME"] + allowed-dots-limit = 3 } ] } From 552704eea332a4175b3af391c5b2d3c983d99d8e Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Sat, 8 Oct 2022 15:30:55 +0530 Subject: [PATCH 22/30] Increase test coverage --- modules/api/src/it/resources/application.conf | 4 ++-- .../RecordSetServiceIntegrationSpec.scala | 22 +++++++++++++++++++ .../api/domain/record/RecordSetService.scala | 4 +++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index 876d39527..aedb547d1 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -168,7 +168,7 @@ vinyldns { # for local testing allowed-settings = [ { - zone = "dummy." + zone = "*mmy." allowed-user-list = ["testuser"] allowed-group-list = ["dummy-group"] allowed-record-type = ["AAAA"] @@ -176,7 +176,7 @@ vinyldns { }, { # for wildcard zones. Settings will be applied to all matching zones - zone = "*ent.com." + zone = "parent.com." allowed-user-list = ["professor", "testuser"] allowed-group-list = ["testing-group"] allowed-record-type = ["A", "CNAME"] diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index 544632028..52bded45e 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -391,6 +391,28 @@ class RecordSetServiceIntegrationSpec .name shouldBe "test.dotted" } + "fail creating dotted record if it satisfies all dotted hosts config except allowed-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 { val newRecord = apexTestRecordA.copy(ttl = 200) val result = testRecordSetService diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index cfd51c9ba..210d2d83a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -130,7 +130,7 @@ class RecordSetService( allowedZoneList, isRecordTypeAndUserAllowed ).toResult - _ <- checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult + _ <- if(allowedZoneList.contains(zone.name)) checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult else ().toResult _ <- messageQueue.send(change).toResult[Unit] } yield change @@ -167,6 +167,7 @@ class RecordSetService( isAllowedUser = isInAllowedUsers || isUserInAllowedGroups isRecordTypeAllowed = checkIfInAllowedRecordType(zone, dottedHostsConfig, rsForValidations) isRecordTypeAndUserAllowed = isAllowedUser && isRecordTypeAllowed + allowedDotsLimit = getAllowedDotsLimit(zone, dottedHostsConfig) recordFqdnDoesNotAlreadyExist <- recordFQDNDoesNotExist(rsForValidations, zone).toResult[Boolean] _ <- typeSpecificValidations( rsForValidations, @@ -178,6 +179,7 @@ class RecordSetService( allowedZoneList, isRecordTypeAndUserAllowed, ).toResult + _ <- if(existing.name == rsForValidations.name) ().toResult else if(allowedZoneList.contains(zone.name)) checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult else ().toResult _ <- messageQueue.send(change).toResult[Unit] } yield change From 3e509d5ce5d53541ea3ae0bff7b9cfbe50fce3d4 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Sat, 8 Oct 2022 16:10:20 +0530 Subject: [PATCH 23/30] Fix test --- .../scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 05978d393..9f613b7e3 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -286,7 +286,7 @@ class RecordSetServiceSpec .getZonesByFilters(record.name.split('.').map(x => x + "." + okZone.name).toSet) doReturn(IO.pure(Set())) .when(mockGroupRepo) - .getGroupsByName(Set.empty) + .getGroupsByName(dottedHostsConfigGroupsAllowed.toSet) doReturn(IO.pure(ListUsersResults(Seq(), None))) .when(mockUserRepo) .getUsers(Set.empty, None, None) From 843dcf20e52af2d5892cbf898b646918198c4cfa Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 11 Oct 2022 18:04:33 +0530 Subject: [PATCH 25/30] Resolve DNS failure --- .../vinyldns/api/domain/record/RecordSetService.scala | 7 ++++--- .../api/domain/record/RecordSetValidations.scala | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 210d2d83a..6913fe7a1 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -92,11 +92,10 @@ class RecordSetService( for { zone <- getZone(recordSet.zoneId) authZones = dottedHostsConfig.authConfigs.map(x => x.zone) - newRs = if(authZones.contains(zone.name) && recordSet.name.takeRight(1) == ".") recordSet.copy(name = recordSet.name.dropRight(1)) else recordSet - change <- RecordSetChangeGenerator.forAdd(newRs, 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 rsForValidations = change.recordSet - _ <- isNotHighValueDomain(newRs, zone, highValueDomainConfig).toResult + _ <- isNotHighValueDomain(recordSet, zone, highValueDomainConfig).toResult _ <- recordSetDoesNotExist( backendResolver.resolve, zone, @@ -131,6 +130,7 @@ class RecordSetService( isRecordTypeAndUserAllowed ).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] } yield change @@ -180,6 +180,7 @@ class RecordSetService( isRecordTypeAndUserAllowed, ).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] } yield change diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 3e59c9088..7fd3ca050 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -365,6 +365,16 @@ object RecordSetValidations { ) } + 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( ownerGroupId: Option[String], group: Option[Group], From adb933a783086579a3e9152f59268e712897f028 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 12 Oct 2022 11:37:35 +0530 Subject: [PATCH 26/30] Address PR comments --- .../api/config/DottedHostsConfig.scala | 10 +- .../api/domain/record/RecordSetService.scala | 46 ++++----- .../domain/record/RecordSetValidations.scala | 37 ++++---- .../vinyldns/api/VinylDNSTestHelpers.scala | 4 +- .../domain/record/RecordSetServiceSpec.scala | 93 +++++++++++++------ .../record/RecordSetValidationsSpec.scala | 32 +++++-- .../scala/vinyldns/core/TestZoneData.scala | 1 + 7 files changed, 142 insertions(+), 81 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index e28f35f3b..2c0721ac7 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -19,13 +19,13 @@ package vinyldns.api.config import pureconfig.ConfigReader import pureconfig.generic.auto._ -final case class AuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String], allowedDotsLimit: Int) -final case class DottedHostsConfig(authConfigs: List[AuthConfigs]) +final case class ZoneAuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String], allowedDotsLimit: Int) +final case class DottedHostsConfig(zoneAuthConfigs: List[ZoneAuthConfigs]) object DottedHostsConfig { implicit val configReader: ConfigReader[DottedHostsConfig] = - ConfigReader.forProduct1[DottedHostsConfig, List[AuthConfigs]]( + ConfigReader.forProduct1[DottedHostsConfig, List[ZoneAuthConfigs]]( "allowed-settings", - )(authConfigs => - DottedHostsConfig(authConfigs)) + )(zoneAuthConfigs => + DottedHostsConfig(zoneAuthConfigs)) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 6913fe7a1..cd699261d 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -28,7 +28,7 @@ import vinyldns.core.queue.MessageQueue import cats.data._ import cats.effect.IO import org.xbill.DNS.ReverseMap -import vinyldns.api.config.{AuthConfigs, DottedHostsConfig, HighValueDomainConfig} +import vinyldns.api.config.{ZoneAuthConfigs, DottedHostsConfig, HighValueDomainConfig} import vinyldns.api.domain.DomainValidations.{validateIpv4Address, validateIpv6Address} import vinyldns.api.domain.access.AccessValidationsAlgebra import vinyldns.core.domain.record.NameSort.NameSort @@ -91,7 +91,7 @@ class RecordSetService( def addRecordSet(recordSet: RecordSet, auth: AuthPrincipal): Result[ZoneCommandResult] = for { zone <- getZone(recordSet.zoneId) - authZones = dottedHostsConfig.authConfigs.map(x => x.zone) + authZones = dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) change <- RecordSetChangeGenerator.forAdd(recordSet, zone, Some(auth)).toResult // because changes happen to the RS in forAdd itself, converting 1st and validating on that rsForValidations = change.recordSet @@ -127,7 +127,8 @@ class RecordSetService( approvedNameServers, recordFqdnDoesNotAlreadyExist, allowedZoneList, - isRecordTypeAndUserAllowed + isRecordTypeAndUserAllowed, + allowedDotsLimit ).toResult _ <- if(allowedZoneList.contains(zone.name)) checkAllowedDots(allowedDotsLimit, rsForValidations, zone).toResult else ().toResult _ <- if(allowedZoneList.contains(zone.name)) isNotApexEndsWithDot(rsForValidations, zone).toResult else ().toResult @@ -160,7 +161,7 @@ class RecordSetService( validateRecordLookupAgainstDnsBackend ) _ <- noCnameWithNewName(rsForValidations, existingRecordsWithName, zone).toResult - authZones = dottedHostsConfig.authConfigs.map(x => x.zone) + 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] @@ -178,6 +179,7 @@ class RecordSetService( recordFqdnDoesNotAlreadyExist, allowedZoneList, isRecordTypeAndUserAllowed, + allowedDotsLimit ).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 @@ -244,16 +246,16 @@ class RecordSetService( // 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.authConfigs.map(x => x.zone) + 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.authConfigs.filter(x => x.zone == zoneName).head.allowedDotsLimit + config.zoneAuthConfigs.filter(x => x.zone == zoneName).head.allowedDotsLimit } else if(isContainWildcardZone){ - config.authConfigs.filter(x => zoneName.matches(x.zone.replace("*", "[A-Za-z0-9.]*"))).head.allowedDotsLimit + config.zoneAuthConfigs.filter(x => zoneName.matches(x.zone.replace("*", "[A-Za-z0-9.]*"))).head.allowedDotsLimit } else { 0 @@ -262,14 +264,14 @@ class RecordSetService( // 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.authConfigs.map(x => x.zone) + 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.authConfigs.flatMap { - x: AuthConfigs => + val users = config.zoneAuthConfigs.flatMap { + x: ZoneAuthConfigs => if (x.zone == zoneName) x.allowedUserList else List.empty } if(users.contains(auth.signedInUser.userName)){ @@ -280,8 +282,8 @@ class RecordSetService( } } else if(isContainWildcardZone){ - val users = config.authConfigs.flatMap { - x: AuthConfigs => + 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.allowedUserList else List.empty @@ -301,14 +303,14 @@ class RecordSetService( // 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.authConfigs.map(x => x.zone) + 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.authConfigs.flatMap { - x: AuthConfigs => + val rType = config.zoneAuthConfigs.flatMap { + x: ZoneAuthConfigs => if (x.zone == zoneName) x.allowedRecordType else List.empty } if(rType.contains(rs.typ.toString)){ @@ -319,8 +321,8 @@ class RecordSetService( } } else if(isContainWildcardZone){ - val rType = config.authConfigs.flatMap { - x: AuthConfigs => + 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.allowedRecordType else List.empty @@ -340,20 +342,20 @@ class RecordSetService( // 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.authConfigs.map(x => x.zone) + 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.authConfigs.flatMap { - x: AuthConfigs => + config.zoneAuthConfigs.flatMap { + x: ZoneAuthConfigs => if (x.zone == zoneName) x.allowedGroupList else List.empty } } else if(isContainWildcardZone){ - config.authConfigs.flatMap { - x: AuthConfigs => + config.zoneAuthConfigs.flatMap { + x: ZoneAuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") if (zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 7fd3ca050..4a625e0cc 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -97,7 +97,8 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet] = None, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], - isRecordTypeAndUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean, + allowedDotsLimit: Int = 0 ): Either[Throwable, Unit] = { val zoneName = if(zone.name.takeRight(1) != ".") zone.name + "." else zone.name @@ -105,7 +106,7 @@ object RecordSetValidations { 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(newRecordSet.name.contains(".") && isDomainAllowed && newRecordSet.name != zone.name) { + if(allowedDotsLimit != 0 && newRecordSet.name.contains(".") && isDomainAllowed && newRecordSet.name != zone.name) { if(!isRecordTypeAndUserAllowed){ isUserAndRecordTypeAuthorized(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, isRecordTypeAndUserAllowed) } @@ -175,16 +176,17 @@ object RecordSetValidations { approvedNameServers: List[Regex], recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], - isRecordTypeAndUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean, + allowedDotsLimit: Int = 0 ): Either[Throwable, Unit] = newRecordSet.typ match { - case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case SOA => soaValidations(newRecordSet, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) + case NS => nsValidations(newRecordSet, zone, existingRecordSet, approvedNameServers, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) + case SOA => soaValidations(newRecordSet, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) case PTR => ptrValidations(newRecordSet, zone) case SRV | TXT | NAPTR => ().asRight // SRV, TXT and NAPTR do not go through dotted host check - case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) - case _ => checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + case DS => dsValidations(newRecordSet, existingRecordsWithName, zone, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) + case _ => checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) } def typeSpecificDeleteValidations(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = @@ -208,7 +210,8 @@ object RecordSetValidations { existingRecordSet: Option[RecordSet] = None, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], - isRecordTypeAndUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean, + allowedDotsLimit: Int = 0 ): Either[Throwable, Unit] = { // cannot create a cname record if a record with the same exists val noRecordWithName = { @@ -241,7 +244,7 @@ object RecordSetValidations { ) _ <- noRecordWithName _ <- RDataWithConsecutiveDots - _ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + _ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) } yield () } @@ -252,7 +255,8 @@ object RecordSetValidations { zone: Zone, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], - isRecordTypeAndUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean, + allowedDotsLimit: Int = 0 ): Either[Throwable, Unit] = { // see https://tools.ietf.org/html/rfc4035#section-2.4 val nsChecks = existingRecordsWithName.find(_.typ == NS) match { @@ -265,7 +269,7 @@ object RecordSetValidations { } for { - _ <- checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) + _ <- checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) _ <- isNotOrigin( newRecordSet, zone, @@ -282,10 +286,11 @@ object RecordSetValidations { approvedNameServers: List[Regex], recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], - isRecordTypeAndUserAllowed: Boolean + isRecordTypeAndUserAllowed: Boolean, + allowedDotsLimit: Int = 0 ): Either[Throwable, Unit] = { // TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically - val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight + val isNotDottedHost = if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) else ().asRight for { _ <- isNotDottedHost @@ -307,9 +312,9 @@ object RecordSetValidations { } yield () } - def soaValidations(newRecordSet: RecordSet, zone: Zone, recordFqdnDoesNotExist: Boolean, dottedHostZoneConfig: Set[String], isRecordTypeAndUserAllowed: Boolean): 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 - if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed) else ().asRight + if (!zone.isReverse) checkForDot(newRecordSet, zone, None, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit) else ().asRight def ptrValidations(newRecordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = // TODO we don't check for PTR as dotted...not sure why diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index 08c4d28e1..94eabc111 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -18,7 +18,7 @@ package vinyldns.api import com.comcast.ip4s.IpAddress import org.joda.time.DateTime -import vinyldns.api.config.{AuthConfigs, BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} +import vinyldns.api.config.{ZoneAuthConfigs, BatchChangeConfig, DottedHostsConfig, HighValueDomainConfig, LimitsConfig, ManualReviewConfig, ScheduledChangesConfig} import vinyldns.api.domain.batch.V6DiscoveryNibbleBoundaries import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ @@ -40,7 +40,7 @@ trait VinylDNSTestHelpers { val approvedNameServers: List[Regex] = List(new Regex("some.test.ns.")) - val dottedHostsConfig: DottedHostsConfig = DottedHostsConfig(List(AuthConfigs("dotted.xyz.",List("xyz"),List("dummy"),List("CNAME"), 3), AuthConfigs("abc.zone.recordsets.",List("locked"),List("dummy"),List("CNAME"), 3), AuthConfigs("xyz.",List("super"),List("xyz"),List("CNAME"), 3))) + 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) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 9f613b7e3..6891fbddd 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -24,7 +24,7 @@ import org.scalatestplus.mockito.MockitoSugar import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.BeforeAndAfterEach -import vinyldns.api.config.{AuthConfigs, DottedHostsConfig} +import vinyldns.api.config.{ZoneAuthConfigs, DottedHostsConfig} import vinyldns.api.{ResultHelpers, VinylDNSTestHelpers} import vinyldns.api.domain.access.AccessValidations import vinyldns.api.domain.record.RecordSetHelpers._ @@ -128,14 +128,14 @@ class RecordSetServiceSpec ) def getDottedHostsConfigGroupsAllowed(zone: Zone, config: DottedHostsConfig): List[String] = { - val configZones = config.authConfigs.map(x => x.zone) + 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.authConfigs.flatMap { - x: AuthConfigs => + 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.allowedGroupList else List.empty @@ -150,7 +150,7 @@ class RecordSetServiceSpec groups } - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(okZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -164,7 +164,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -234,7 +234,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -304,7 +304,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -350,7 +350,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -389,7 +389,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -447,7 +447,7 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(okGroup))) .when(mockGroupRepo) .getGroup(okGroup.id) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -521,7 +521,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -560,7 +560,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -571,7 +571,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(dottedZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -604,7 +604,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = xyzZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(xyzZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -644,6 +644,47 @@ class RecordSetServiceSpec result.recordSet.name shouldBe record.name } + "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) @@ -654,7 +695,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -684,7 +725,7 @@ class RecordSetServiceSpec val record = cname.copy(name = "new.name", zoneId = abcZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(abcZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -725,8 +766,8 @@ class RecordSetServiceSpec val record = aaaa.copy(name = "new.name", zoneId = dottedZone.id, status = RecordSetStatus.Active) - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map { - case y:AuthConfigs => y.zone + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map { + case y:ZoneAuthConfigs => y.zone } val dottedHostsConfigGroupsAllowed: List[String] = getDottedHostsConfigGroupsAllowed(dottedZone, VinylDNSTestHelpers.dottedHostsConfig) @@ -738,7 +779,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(dottedZone.id, record.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -779,7 +820,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -836,7 +877,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -896,7 +937,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -939,7 +980,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -982,7 +1023,7 @@ class RecordSetServiceSpec doReturn(IO.pure(List())) .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) @@ -1138,7 +1179,7 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(oneUserDummyGroup))) .when(mockGroupRepo) .getGroup(oneUserDummyGroup.id) - doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone))) + doReturn(IO.pure(Set(dottedZone, abcZone, xyzZone, dotZone))) .when(mockZoneRepo) .getZonesByNames(dottedHostsConfigZonesAllowed.toSet) doReturn(IO.pure(Set())) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index 10fd0a99e..e0a03905e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -44,7 +44,7 @@ class RecordSetValidationsSpec import RecordSetValidations._ - val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.authConfigs.map(x => x.zone) + val dottedHostsConfigZonesAllowed: List[String] = VinylDNSTestHelpers.dottedHostsConfig.zoneAuthConfigs.map(x => x.zone) "RecordSetValidations" should { "validRecordTypes" should { @@ -232,18 +232,30 @@ class RecordSetValidationsSpec } "return a success for any new record with dotted hosts in forward zones if it satisfies dotted hosts configs" in { - val record = typeSpecificValidations(dottedARecord.copy(zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) + // 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 in forward zones (CNAME) if it satisfies dotted hosts configs" in { - val record = typeSpecificValidations(dottedARecord.copy(typ = CNAME, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) - 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 (NS) if it satisfies dotted hosts configs" in { - val record = typeSpecificValidations(dottedARecord.copy(typ = NS, zoneId = dottedZone.id), List(), dottedZone, None, Nil, true, dottedHostsConfigZonesAllowed.toSet, true) - record should be(right) + "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] } "return a success for any existing record with dotted hosts in forward zones" in { @@ -445,7 +457,7 @@ class RecordSetValidationsSpec } "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) + 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 { @@ -508,7 +520,7 @@ class RecordSetValidationsSpec } "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) + 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 { diff --git a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala index 34c1495f1..df08e8a0a 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala @@ -35,6 +35,7 @@ object TestZoneData { 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 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) From a0448b922b5a334bc024dcfb6bf186374398ca4b Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 12 Oct 2022 11:54:47 +0530 Subject: [PATCH 27/30] Update docs --- modules/docs/src/main/mdoc/operator/config-api.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index d32df144d..9e31806ef 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -552,6 +552,12 @@ Note the following: not necessary for the user to be in both `allowed-user-list` and `allowed-group-list`. 6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. 7. The number of dots allowed in a record name for a zone is given in `allowed-dots-limit`. +8. If `allowed-user-list` is left empty (`allowed-user-list = []`), no user will be allowed to create dotted hosts unless +they're present in `allowed-group-list` and vice-versa. If both `allowed-user-list` and `allowed-group-list` is left empty +no users will be allowed to create dotted hosts in that zone. +9. If `allowed-record-type` is left empty (`allowed-record-type = []`), user cannot create dotted hosts of any record type +in that zone. +10. If `allowed-dots-limit` is set to 0 (`allowed-dots-limit = 0`), we cannot create dotted hosts record in that zone. ```yaml # approved zones, individual users, users in groups and record types that are allowed for dotted hosts From 3527fdb72264854968638d9dd32d32cba1595812 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 12 Oct 2022 12:22:06 +0530 Subject: [PATCH 28/30] Add test --- .../domain/record/RecordSetServiceSpec.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 6891fbddd..43b5ffc0c 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -644,6 +644,47 @@ class RecordSetServiceSpec 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) From 6b0b1a32f6adf8c41b9088e2920bac2d32fa95ed Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 14 Oct 2022 12:02:22 +0530 Subject: [PATCH 29/30] Rename config properties --- modules/api/src/it/resources/application.conf | 16 ++--- .../RecordSetServiceIntegrationSpec.scala | 2 +- modules/api/src/main/resources/reference.conf | 16 ++--- .../api/config/DottedHostsConfig.scala | 2 +- .../api/domain/record/RecordSetService.scala | 16 ++--- .../domain/record/RecordSetValidations.scala | 2 +- .../tests/recordsets/create_recordset_test.py | 4 +- .../domain/record/RecordSetServiceSpec.scala | 4 +- .../docs/src/main/mdoc/operator/config-api.md | 58 +++++++++---------- 9 files changed, 60 insertions(+), 60 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index aedb547d1..550af4139 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -169,18 +169,18 @@ vinyldns { allowed-settings = [ { zone = "*mmy." - allowed-user-list = ["testuser"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["AAAA"] - allowed-dots-limit = 3 + 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." - allowed-user-list = ["professor", "testuser"] - allowed-group-list = ["testing-group"] - allowed-record-type = ["A", "CNAME"] - allowed-dots-limit = 3 + user-list = ["professor", "testuser"] + group-list = ["testing-group"] + record-types = ["A", "CNAME"] + dots-limit = 3 } ] } diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index 52bded45e..c789d8305 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -391,7 +391,7 @@ class RecordSetServiceIntegrationSpec .name shouldBe "test.dotted" } - "fail creating dotted record if it satisfies all dotted hosts config except allowed-dots-limit for the zone" in { + "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", diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 021ad67ce..402a8b1e6 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -97,18 +97,18 @@ vinyldns { { # for wildcard zones. Settings will be applied to all matching zones zone = "*ent.com*." - allowed-user-list = ["ok"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["CNAME"] - allowed-dots-limit = 3 + 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*." - allowed-user-list = ["sharedZoneUser"] - allowed-group-list = ["history-group1"] - allowed-record-type = ["A"] - allowed-dots-limit = 3 + user-list = ["sharedZoneUser"] + group-list = ["history-group1"] + record-types = ["A"] + dots-limit = 3 } ] } diff --git a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala index 2c0721ac7..a5d1e546d 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/DottedHostsConfig.scala @@ -19,7 +19,7 @@ package vinyldns.api.config import pureconfig.ConfigReader import pureconfig.generic.auto._ -final case class ZoneAuthConfigs(zone: String, allowedUserList: List[String], allowedGroupList: List[String], allowedRecordType: List[String], allowedDotsLimit: Int) +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 { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index cd699261d..0a38bae89 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -252,10 +252,10 @@ class RecordSetService( val isContainWildcardZone = dottedZoneConfig.exists(x => zoneName.matches(x)) val isContainNormalZone = configZones.contains(zoneName) if(isContainNormalZone){ - config.zoneAuthConfigs.filter(x => x.zone == zoneName).head.allowedDotsLimit + 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.allowedDotsLimit + config.zoneAuthConfigs.filter(x => zoneName.matches(x.zone.replace("*", "[A-Za-z0-9.]*"))).head.dotsLimit } else { 0 @@ -272,7 +272,7 @@ class RecordSetService( if(isContainNormalZone){ val users = config.zoneAuthConfigs.flatMap { x: ZoneAuthConfigs => - if (x.zone == zoneName) x.allowedUserList else List.empty + if (x.zone == zoneName) x.userList else List.empty } if(users.contains(auth.signedInUser.userName)){ true @@ -286,7 +286,7 @@ class RecordSetService( x: ZoneAuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if (zoneName.matches(wildcardZone)) x.allowedUserList else List.empty + if (zoneName.matches(wildcardZone)) x.userList else List.empty } else List.empty } if(users.contains(auth.signedInUser.userName)){ @@ -311,7 +311,7 @@ class RecordSetService( if(isContainNormalZone){ val rType = config.zoneAuthConfigs.flatMap { x: ZoneAuthConfigs => - if (x.zone == zoneName) x.allowedRecordType else List.empty + if (x.zone == zoneName) x.recordTypes else List.empty } if(rType.contains(rs.typ.toString)){ true @@ -325,7 +325,7 @@ class RecordSetService( x: ZoneAuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if (zoneName.matches(wildcardZone)) x.allowedRecordType else List.empty + if (zoneName.matches(wildcardZone)) x.recordTypes else List.empty } else List.empty } if(rType.contains(rs.typ.toString)){ @@ -350,7 +350,7 @@ class RecordSetService( val groups = if(isContainNormalZone){ config.zoneAuthConfigs.flatMap { x: ZoneAuthConfigs => - if (x.zone == zoneName) x.allowedGroupList else List.empty + if (x.zone == zoneName) x.groupList else List.empty } } else if(isContainWildcardZone){ @@ -358,7 +358,7 @@ class RecordSetService( x: ZoneAuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z0-9.]*") - if (zoneName.matches(wildcardZone)) x.allowedGroupList else List.empty + if (zoneName.matches(wildcardZone)) x.groupList else List.empty } else List.empty } } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 4a625e0cc..cbbd35b60 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -361,7 +361,7 @@ object RecordSetValidations { ensuring( InvalidRequest( s"RecordSet with name ${recordSet.name} has more dots than that is allowed in config for this zone " + - s"which is, 'allowed-dots-limit = $allowedDotsLimit'." + s"which is, 'dots-limit = $allowedDotsLimit'." ) )( recordSet.name.count(_ == '.') <= allowedDotsLimit || (recordSet.name.count(_ == '.') == 1 && diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index d6c9a6cff..f04152c13 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -562,7 +562,7 @@ def test_create_dotted_a_record_fails_if_all_dotted_hosts_config_not_satisfied(s 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 'allowed-dots-limit' config from dotted-hosts config is not satisfied. Config present in reference.conf + 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 @@ -576,7 +576,7 @@ def test_create_dotted_a_record_fails_if_all_dotted_hosts_config_not_satisfied(s 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, 'allowed-dots-limit = 3'.")) + "allowed in config for this zone which is, 'dots-limit = 3'.")) def test_create_dotted_a_record_apex_succeeds(shared_zone_test_context): diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 43b5ffc0c..520a4bd9e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -138,9 +138,9 @@ class RecordSetServiceSpec x: ZoneAuthConfigs => if (x.zone.contains("*")) { val wildcardZone = x.zone.replace("*", "[A-Za-z.]*") - if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.allowedGroupList else List.empty + if (zoneName.substring(0, zoneName.length - 1).matches(wildcardZone)) x.groupList else List.empty } else { - if (x.zone == zoneName) x.allowedGroupList else List.empty + if (x.zone == zoneName) x.groupList else List.empty } } } diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 9e31806ef..db3e449dc 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -546,18 +546,18 @@ allowed to create dotted hosts. If only all the above are satisfied, one can cre 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 `allowed-user-list` using their username. -4. A set of users in a group who are allowed to create dotted hosts are added to the `allowed-group-list` using group name. -5. If the user is either in `allowed-user-list` or `allowed-group-list`, they are allowed to create a dotted host. It is -not necessary for the user to be in both `allowed-user-list` and `allowed-group-list`. -6. The record types which are allowed while creating a dotted host is added to the `allowed-record-type`. -7. The number of dots allowed in a record name for a zone is given in `allowed-dots-limit`. -8. If `allowed-user-list` is left empty (`allowed-user-list = []`), no user will be allowed to create dotted hosts unless -they're present in `allowed-group-list` and vice-versa. If both `allowed-user-list` and `allowed-group-list` is left empty +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 `allowed-record-type` is left empty (`allowed-record-type = []`), user cannot create dotted hosts of any record type +9. If `record-types` is left empty (`record-types = []`), user cannot create dotted hosts of any record type in that zone. -10. If `allowed-dots-limit` is set to 0 (`allowed-dots-limit = 0`), we cannot create dotted hosts record 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 and record types that are allowed for dotted hosts @@ -565,18 +565,18 @@ dotted-hosts = { allowed-settings = [ { zone = "dummy." - allowed-user-list = ["testuser"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["AAAA"] - allowed-dots-limit = 3 + 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." - allowed-user-list = ["professor", "testuser"] - allowed-group-list = ["testing-group"] - allowed-record-type = ["A", "CNAME"] - allowed-dots-limit = 3 + user-list = ["professor", "testuser"] + group-list = ["testing-group"] + record-types = ["A", "CNAME"] + dots-limit = 3 } ] } @@ -586,9 +586,9 @@ In the above, the dotted hosts can be created only in the zone `dummy.` and zone 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 `allowed-record-type`. -And the user `professor` can't create a dotted host in the zone `dummy.` as the user is not in `allowed-user-list` or -`allowed-group-list` (not part of `dummy-group`). +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: @@ -777,18 +777,18 @@ dotted-hosts = { allowed-settings = [ { zone = "dummy." - allowed-user-list = ["testuser"] - allowed-group-list = ["dummy-group"] - allowed-record-type = ["AAAA"] - allowed-dots-limit = 3 + 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." - allowed-user-list = ["professor", "testuser"] - allowed-group-list = ["testing-group"] - allowed-record-type = ["A", "CNAME"] - allowed-dots-limit = 3 + user-list = ["professor", "testuser"] + group-list = ["testing-group"] + record-types = ["A", "CNAME"] + dots-limit = 3 } ] } From da7240ae59278a3fbd2cf908f0e40428f0570d39 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 14 Oct 2022 13:20:31 +0530 Subject: [PATCH 30/30] Update config --- modules/api/src/it/resources/application.conf | 2 +- modules/api/src/main/resources/application.conf | 12 ++++++++++-- modules/api/src/main/resources/reference.conf | 2 +- modules/docs/src/main/mdoc/operator/config-api.md | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index 550af4139..dd3b7006e 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -163,7 +163,7 @@ vinyldns { "ns1.parent.com4." ] - # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + # 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 = [ diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 1667d9298..7407c07e4 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -165,9 +165,17 @@ vinyldns { "ns1.parent.com4." ] - # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + # approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts dotted-hosts = { - allowed-settings = [] + 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 diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 402a8b1e6..f7a74e2f9 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -90,7 +90,7 @@ vinyldns { "ns1.parent.com." ] - # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + # 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 = [ diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index db3e449dc..fa8b69ba3 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -560,7 +560,7 @@ 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 and record types that are allowed for dotted hosts +# approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts dotted-hosts = { allowed-settings = [ { @@ -772,7 +772,7 @@ dotted-hosts = { } } - # approved zones, individual users, users in groups and record types that are allowed for dotted hosts + # approved zones, individual users, users in groups, record types and no.of.dots that are allowed for dotted hosts dotted-hosts = { allowed-settings = [ {